From aa0e3185435f2fa320305dd068338f790005a197 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Feb 2022 15:02:43 -0500 Subject: [PATCH] add RtMidi for eventual MIDI support --- CMakeLists.txt | 17 + extern/rtmidi/CMakeLists.txt | 278 ++ extern/rtmidi/LICENSE | 27 + extern/rtmidi/Makefile.am | 21 + extern/rtmidi/README.md | 39 + extern/rtmidi/RtMidi.cpp | 3934 +++++++++++++++++ extern/rtmidi/RtMidi.h | 658 +++ extern/rtmidi/autogen.sh | 107 + extern/rtmidi/cmake/RtMidi-config.cmake.in | 20 + .../cmake/RtMidiConfigUninstall.cmake.in | 21 + extern/rtmidi/configure.ac | 254 ++ extern/rtmidi/contrib/go/rtmidi/rtmidi.go | 355 ++ .../rtmidi/contrib/go/rtmidi/rtmidi_stub.cpp | 4 + extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.h | 1 + .../rtmidi/contrib/go/rtmidi/rtmidi_test.go | 46 + extern/rtmidi/doc/Makefile.am | 33 + extern/rtmidi/doc/doxygen/Doxyfile.in | 1865 ++++++++ extern/rtmidi/doc/doxygen/footer.html | 9 + extern/rtmidi/doc/doxygen/header.html | 9 + .../doc/doxygen/samples/getting_started.cpp | 11 + extern/rtmidi/doc/doxygen/tutorial.txt | 484 ++ extern/rtmidi/doc/images/ccrma.gif | Bin 0 -> 3527 bytes extern/rtmidi/doc/images/mcgill.gif | Bin 0 -> 4614 bytes extern/rtmidi/doc/release.txt | 141 + extern/rtmidi/m4/ax_cxx_compile_stdcxx.m4 | 951 ++++ extern/rtmidi/msw/readme | 1 + extern/rtmidi/msw/rtmidilib.sln | 20 + extern/rtmidi/msw/rtmidilib.vcproj | 186 + extern/rtmidi/rtmidi-config.in | 19 + extern/rtmidi/rtmidi.pc.in | 12 + extern/rtmidi/rtmidi_c.cpp | 372 ++ extern/rtmidi/rtmidi_c.h | 251 ++ extern/rtmidi/tests/Debug/.placeholder | 0 extern/rtmidi/tests/Makefile.am | 38 + extern/rtmidi/tests/Release/.placeholder | 0 extern/rtmidi/tests/RtMidi.dsw | 77 + extern/rtmidi/tests/apinames.cpp | 159 + extern/rtmidi/tests/cmidiin.cpp | 111 + extern/rtmidi/tests/cmidiin.dsp | 110 + extern/rtmidi/tests/midiclock.cpp | 231 + extern/rtmidi/tests/midiout.cpp | 146 + extern/rtmidi/tests/midiout.dsp | 110 + extern/rtmidi/tests/midiprobe.cpp | 76 + extern/rtmidi/tests/midiprobe.dsp | 110 + extern/rtmidi/tests/qmidiin.cpp | 98 + extern/rtmidi/tests/qmidiin.dsp | 110 + extern/rtmidi/tests/sysextest.cpp | 152 + extern/rtmidi/tests/sysextest.dsp | 110 + extern/rtmidi/tests/testcapi.c | 26 + src/audio/rtmidi.h | 1 + src/audio/taAudio.h | 70 + 51 files changed, 11881 insertions(+) create mode 100644 extern/rtmidi/CMakeLists.txt create mode 100644 extern/rtmidi/LICENSE create mode 100644 extern/rtmidi/Makefile.am create mode 100644 extern/rtmidi/README.md create mode 100644 extern/rtmidi/RtMidi.cpp create mode 100644 extern/rtmidi/RtMidi.h create mode 100755 extern/rtmidi/autogen.sh create mode 100644 extern/rtmidi/cmake/RtMidi-config.cmake.in create mode 100644 extern/rtmidi/cmake/RtMidiConfigUninstall.cmake.in create mode 100644 extern/rtmidi/configure.ac create mode 100644 extern/rtmidi/contrib/go/rtmidi/rtmidi.go create mode 100644 extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.cpp create mode 100644 extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.h create mode 100644 extern/rtmidi/contrib/go/rtmidi/rtmidi_test.go create mode 100644 extern/rtmidi/doc/Makefile.am create mode 100644 extern/rtmidi/doc/doxygen/Doxyfile.in create mode 100644 extern/rtmidi/doc/doxygen/footer.html create mode 100644 extern/rtmidi/doc/doxygen/header.html create mode 100644 extern/rtmidi/doc/doxygen/samples/getting_started.cpp create mode 100644 extern/rtmidi/doc/doxygen/tutorial.txt create mode 100644 extern/rtmidi/doc/images/ccrma.gif create mode 100644 extern/rtmidi/doc/images/mcgill.gif create mode 100644 extern/rtmidi/doc/release.txt create mode 100644 extern/rtmidi/m4/ax_cxx_compile_stdcxx.m4 create mode 100644 extern/rtmidi/msw/readme create mode 100755 extern/rtmidi/msw/rtmidilib.sln create mode 100755 extern/rtmidi/msw/rtmidilib.vcproj create mode 100644 extern/rtmidi/rtmidi-config.in create mode 100644 extern/rtmidi/rtmidi.pc.in create mode 100644 extern/rtmidi/rtmidi_c.cpp create mode 100644 extern/rtmidi/rtmidi_c.h create mode 100644 extern/rtmidi/tests/Debug/.placeholder create mode 100644 extern/rtmidi/tests/Makefile.am create mode 100644 extern/rtmidi/tests/Release/.placeholder create mode 100644 extern/rtmidi/tests/RtMidi.dsw create mode 100644 extern/rtmidi/tests/apinames.cpp create mode 100644 extern/rtmidi/tests/cmidiin.cpp create mode 100644 extern/rtmidi/tests/cmidiin.dsp create mode 100644 extern/rtmidi/tests/midiclock.cpp create mode 100644 extern/rtmidi/tests/midiout.cpp create mode 100644 extern/rtmidi/tests/midiout.dsp create mode 100644 extern/rtmidi/tests/midiprobe.cpp create mode 100644 extern/rtmidi/tests/midiprobe.dsp create mode 100644 extern/rtmidi/tests/qmidiin.cpp create mode 100644 extern/rtmidi/tests/qmidiin.dsp create mode 100644 extern/rtmidi/tests/sysextest.cpp create mode 100644 extern/rtmidi/tests/sysextest.dsp create mode 100644 extern/rtmidi/tests/testcapi.c create mode 100644 src/audio/rtmidi.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 011b3ee9..9e466ddf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ option(BUILD_GUI "Build the tracker (disable to build only a headless player)" $ option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT}) option(SYSTEM_FMT "Use a system-installed version of fmt instead of the vendored one" OFF) option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead of the vendored one" OFF) +option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the vendored one" OFF) option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF) option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT}) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) @@ -102,6 +103,22 @@ else() message(STATUS "Using vendored libsndfile") endif() +if (SYSTEM_RTMIDI) + find_package(PkgConfig REQUIRED) + pkg_check_modules(RTMIDI REQUIRED rtmidi) + list(APPEND DEPENDENCIES_INCLUDE_DIRS ${RTMIDI_INCLUDE_DIRS}) + list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${RTMIDI_CFLAGS_OTHER}) + list(APPEND DEPENDENCIES_LIBRARIES ${RTMIDI_LIBRARIES}) + list(APPEND DEPENDENCIES_LIBRARY_DIRS ${RTMIDI_LIBRARY_DIRS}) + list(APPEND DEPENDENCIES_LINK_OPTIONS ${RTMIDI_LDFLAGS_OTHER}) + list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${RTMIDI_LDFLAGS}) + message(STATUS "Using system-installed RtMidi") +else() + add_subdirectory(extern/rtmidi EXCLUDE_FROM_ALL) + list(APPEND DEPENDENCIES_LIBRARIES rtmidi) + message(STATUS "Using vendored RtMidi") +endif() + if (SYSTEM_ZLIB) find_package(PkgConfig REQUIRED) pkg_check_modules(ZLIB REQUIRED zlib) diff --git a/extern/rtmidi/CMakeLists.txt b/extern/rtmidi/CMakeLists.txt new file mode 100644 index 00000000..b59a6997 --- /dev/null +++ b/extern/rtmidi/CMakeLists.txt @@ -0,0 +1,278 @@ +# 2018 (c) Juan G. Victores, Bartek Ɓukawski, Stephen Sinclair +# CopyPolicy: RtMidi license. +# additional modifications for Furnace by tildearrow. + +# Set minimum CMake required version for this project. +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +# Define a C++ project. +project(RtMidi LANGUAGES CXX C) + +# standards version +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Check for Jack (any OS) +# RtMidi JACK is not going to be used as I will use my own JACK MIDI implementation. +set(HAVE_JACK FALSE) +#find_library(JACK_LIB jack) +#find_package(PkgConfig) +#pkg_check_modules(jack jack) +#if(JACK_LIB OR jack_FOUND) +# set(HAVE_JACK TRUE) +#endif() + +# Necessary for Windows +#if(WIN32) +# set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +#endif() + +# Standard CMake options +option(BUILD_SHARED_LIBS "Build as shared library" OFF) + +#if (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) +# set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel") +#endif() +#if(WINDOWS) +# set(CMAKE_DEBUG_POSTFIX d CACHE STRING "Postfix for debug version of library") +#endif() + +# Build Options +#set(RTMIDI_TARGETNAME_UNINSTALL "uninstall" CACHE STRING "Name of 'uninstall' build target") + +# API Options +#option(RTMIDI_API_JACK "Compile with JACK support." ${HAVE_JACK}) +if(UNIX AND NOT APPLE) + option(RTMIDI_API_ALSA "Compile with ALSA support." ON) +endif() +option(RTMIDI_API_WINMM "Compile with WINMM support." ${WIN32}) +option(RTMIDI_API_CORE "Compile with CoreMIDI support." ${APPLE}) + +# Add -Wall if possible +#if (CMAKE_COMPILER_IS_GNUCXX) +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +#endif (CMAKE_COMPILER_IS_GNUCXX) + +# Add debug flags +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-D__RTMIDI_DEBUG__) + if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + endif (CMAKE_COMPILER_IS_GNUCXX) +endif () + +# Read libtool version info from configure.ac +#set(R "m4_define\\(\\[lt_([a-z]+)\\], ([0-9]+)\\)") +#file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/configure.ac" CONFIGAC +# REGEX ${R}) +#foreach(_S ${CONFIGAC}) +# string(REGEX REPLACE ${R} "\\1" k ${_S}) +# string(REGEX REPLACE ${R} "\\2" v ${_S}) +# set(SO_${k} ${v}) +#endforeach() +#math(EXPR SO_current_minus_age "${SO_current} - ${SO_age}") +#set(SO_VER "${SO_current_minus_age}") +#set(FULL_VER "${SO_current_minus_age}.${SO_revision}.${SO_age}") + +# Read package version info from configure.ac +#set(R "AC_INIT\\(RtMidi, ([0-9\\.]+),.*\\)") +#file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/configure.ac" CONFIGAC +# REGEX ${R}) +#string(REGEX REPLACE ${R} "\\1" PACKAGE_VERSION ${CONFIGAC}) + +# Init variables +set(rtmidi_SOURCES RtMidi.cpp RtMidi.h rtmidi_c.cpp rtmidi_c.h) +set(LINKLIBS) +set(PUBLICLINKLIBS) +set(INCDIRS) +set(PKGCONFIG_REQUIRES) +set(LIBS_REQUIRES) +set(API_DEFS) +set(API_LIST) + +# Tweak API-specific configuration. + +# ALSA +if(RTMIDI_API_ALSA) + set(NEED_PTHREAD ON) + find_package(ALSA) + if (NOT ALSA_FOUND) + message(FATAL_ERROR "ALSA API requested but no ALSA dev libraries found") + endif() + list(APPEND INCDIRS ${ALSA_INCLUDE_DIR}) + list(APPEND LINKLIBS ${ALSA_LIBRARY}) + list(APPEND PKGCONFIG_REQUIRES "alsa") + list(APPEND API_DEFS "-D__LINUX_ALSA__") + list(APPEND API_LIST "alsa") +endif() + +# WinMM +if(RTMIDI_API_WINMM) + list(APPEND API_DEFS "-D__WINDOWS_MM__") + list(APPEND API_LIST "winmm") + list(APPEND LINKLIBS winmm) +endif() + +# CoreMIDI +if(RTMIDI_API_CORE) + find_library(CORESERVICES_LIB CoreServices) + find_library(COREAUDIO_LIB CoreAudio) + find_library(COREMIDI_LIB CoreMIDI) + find_library(COREFOUNDATION_LIB CoreFoundation) + list(APPEND API_DEFS "-D__MACOSX_CORE__") + list(APPEND API_LIST "coremidi") + list(APPEND LINKLIBS ${CORESERVICES_LIB} ${COREAUDIO_LIB} ${COREMIDI_LIB} ${COREFOUNDATION_LIB}) + list(APPEND LIBS_REQUIRES "-framework CoreServices -framework CoreAudio -framework CoreMIDI -framework CoreFoundation") + list(APPEND LINKFLAGS "-Wl,-F/Library/Frameworks") +endif() + +# pthread +if (NEED_PTHREAD) + find_package(Threads REQUIRED + CMAKE_THREAD_PREFER_PTHREAD + THREADS_PREFER_PTHREAD_FLAG) + list(APPEND PUBLICLINKLIBS Threads::Threads) +endif() + +# Create library targets. +set(LIB_TARGETS) + +# Use RTMIDI_BUILD_SHARED_LIBS / RTMIDI_BUILD_STATIC_LIBS if they +# are defined, otherwise default to standard BUILD_SHARED_LIBS. +if (DEFINED RTMIDI_BUILD_SHARED_LIBS AND NOT RTMIDI_BUILD_SHARED_LIBS STREQUAL "") + if (RTMIDI_BUILD_SHARED_LIBS) + add_library(rtmidi SHARED ${rtmidi_SOURCES}) + else() + add_library(rtmidi STATIC ${rtmidi_SOURCES}) + endif() +elseif (DEFINED RTMIDI_BUILD_STATIC_LIBS AND NOT RTMIDI_BUILD_STATIC_LIBS STREQUAL "") + if (RTMIDI_BUILD_STATIC_LIBS) + add_library(rtmidi STATIC ${rtmidi_SOURCES}) + else() + add_library(rtmidi SHARED ${rtmidi_SOURCES}) + endif() +else() + add_library(rtmidi ${rtmidi_SOURCES}) +endif() +list(APPEND LIB_TARGETS rtmidi) + +# Add headers destination for install rule. +#set_property(TARGET rtmidi PROPERTY PUBLIC_HEADER RtMidi.h rtmidi_c.h) +#set_target_properties(rtmidi PROPERTIES +# SOVERSION ${SO_VER} +# VERSION ${FULL_VER}) + +# Set include paths, populate target interface. +target_include_directories(rtmidi PRIVATE ${INCDIRS} + PUBLIC + $ + $) + +# Set compile-time definitions +target_compile_definitions(rtmidi PRIVATE ${API_DEFS}) +target_compile_definitions(rtmidi PRIVATE RTMIDI_EXPORT) +target_link_libraries(rtmidi PUBLIC ${PUBLICLINKLIBS} + PRIVATE ${LINKLIBS}) + +# Set standard installation directories. +#include(GNUInstallDirs) + +# Add tests if requested. +#option(RTMIDI_BUILD_TESTING "Build test programs" ON) +#if (NOT DEFINED RTMIDI_BUILD_TESTING OR RTMIDI_BUILD_TESTING STREQUAL "") +# set(RTMIDI_BUILD_TESTING ${BUILD_TESTING}) +#endif() +#if (RTMIDI_BUILD_TESTING) +# include(CTest) +# add_executable(cmidiin tests/cmidiin.cpp) +# add_executable(midiclock tests/midiclock.cpp) +# add_executable(midiout tests/midiout.cpp) +# add_executable(midiprobe tests/midiprobe.cpp) +# add_executable(qmidiin tests/qmidiin.cpp) +# add_executable(sysextest tests/sysextest.cpp) +# add_executable(apinames tests/apinames.cpp) +# add_executable(testcapi tests/testcapi.c) +# list(GET LIB_TARGETS 0 LIBRTMIDI) +# set_target_properties(cmidiin midiclock midiout midiprobe qmidiin sysextest apinames testcapi +# PROPERTIES RUNTIME_OUTPUT_DIRECTORY tests +# INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} +# LINK_LIBRARIES ${LIBRTMIDI}) +# add_test(NAME apinames COMMAND apinames) +#endif() + +# Set standard installation directories. +#include(GNUInstallDirs) + +# Message +string(REPLACE ";" " " apilist "${API_LIST}") +message(STATUS "Compiling with support for: ${apilist}") + +# PkgConfig file +#string(REPLACE ";" " " req "${PKGCONFIG_REQUIRES}") +#string(REPLACE ";" " " req_libs "${LIBS_REQUIRES}") +#string(REPLACE ";" " " api "${API_DEFS}") +#set(prefix ${CMAKE_INSTALL_PREFIX}) +#configure_file("${CMAKE_CURRENT_SOURCE_DIR}/rtmidi.pc.in" "rtmidi.pc" @ONLY) + +# Add install rule. +#install(TARGETS ${LIB_TARGETS} +# EXPORT RtMidiTargets +# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +# ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +# PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rtmidi) + +# Store the package in the user registry. +#export(PACKAGE RtMidi) + +# Set installation path for CMake files. +#set(RTMIDI_CMAKE_DESTINATION share/rtmidi) + +# Export library target (build-tree). +#export(EXPORT RtMidiTargets +# NAMESPACE RtMidi::) + +# Export library target (install-tree). +#install(EXPORT RtMidiTargets +# DESTINATION ${RTMIDI_CMAKE_DESTINATION} +# NAMESPACE RtMidi::) + +# Configure uninstall target. +#configure_file( +# "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RtMidiConfigUninstall.cmake.in" +# "${CMAKE_BINARY_DIR}/RtMidiConfigUninstall.cmake" @ONLY) + +# Create uninstall target. +#add_custom_target(${RTMIDI_TARGETNAME_UNINSTALL} +# COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/RtMidiConfigUninstall.cmake) + +#install( +# FILES ${CMAKE_CURRENT_BINARY_DIR}/rtmidi.pc +# DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +# Set up CMake package +#include(CMakePackageConfigHelpers) + +# Write cmake package version file +#write_basic_package_version_file( +# RtMidi-config-version.cmake +# VERSION ${FULL_VER} +# COMPATIBILITY SameMajorVersion +#) + +# Write cmake package config file +#configure_package_config_file ( +# cmake/RtMidi-config.cmake.in +# RtMidi-config.cmake +# INSTALL_DESTINATION "${RTMIDI_CMAKE_DESTINATION}" +#) + +# Install package files +#install ( +# FILES +# "${CMAKE_CURRENT_BINARY_DIR}/RtMidi-config.cmake" +# "${CMAKE_CURRENT_BINARY_DIR}/RtMidi-config-version.cmake" +# DESTINATION +# "${RTMIDI_CMAKE_DESTINATION}" +#) diff --git a/extern/rtmidi/LICENSE b/extern/rtmidi/LICENSE new file mode 100644 index 00000000..313fd19b --- /dev/null +++ b/extern/rtmidi/LICENSE @@ -0,0 +1,27 @@ + +RtMidi: realtime MIDI i/o C++ classes +Copyright (c) 2003-2021 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. diff --git a/extern/rtmidi/Makefile.am b/extern/rtmidi/Makefile.am new file mode 100644 index 00000000..482c5e06 --- /dev/null +++ b/extern/rtmidi/Makefile.am @@ -0,0 +1,21 @@ +SUBDIRS = . tests +if MAKE_DOC +SUBDIRS += doc +endif + +lib_LTLIBRARIES = %D%/librtmidi.la +%C%_librtmidi_la_CXXFLAGS = -DRTMIDI_EXPORT +%C%_librtmidi_la_LDFLAGS = -no-undefined -export-dynamic -version-info @SO_VERSION@ +%C%_librtmidi_la_SOURCES = \ + %D%/RtMidi.cpp \ + %D%/rtmidi_c.cpp + +rtmidi_incdir = $(includedir)/rtmidi +rtmidi_inc_HEADERS = \ + %D%/RtMidi.h \ + %D%/rtmidi_c.h + +pkgconfigdatadir = $(libdir)/pkgconfig +pkgconfigdata_DATA = rtmidi.pc + +EXTRA_DIST = autogen.sh README.md msw rtmidi-config.in contrib cmake CMakeLists.txt diff --git a/extern/rtmidi/README.md b/extern/rtmidi/README.md new file mode 100644 index 00000000..bdfab025 --- /dev/null +++ b/extern/rtmidi/README.md @@ -0,0 +1,39 @@ +# RtMidi + +![Build Status](https://github.com/thestk/rtmidi/actions/workflows/ci.yml/badge.svg) + +A set of C++ classes that provide a common API for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMIDI & JACK) and Windows (Multimedia). + +By Gary P. Scavone, 2003-2021. + +This distribution of RtMidi contains the following: + +- `doc`: RtMidi documentation (also online at http://www.music.mcgill.ca/~gary/rtmidi/) +- `tests`: example RtMidi programs + +On Unix systems, type `./configure` in the top level directory, then `make` in the `tests/` directory to compile the test programs. In Windows, open the Visual C++ workspace file located in the `tests/` directory. + +If you checked out the code from git, please run `./autogen.sh` before `./configure`. + +## Overview + +RtMidi is a set of C++ classes (`RtMidiIn`, `RtMidiOut`, and API specific classes) that provide a common API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA, JACK), Macintosh OS X (CoreMIDI, JACK), and Windows (Multimedia Library) operating systems. RtMidi significantly simplifies the process of interacting with computer MIDI hardware and software. It was designed with the following goals: + + - object oriented C++ design + - simple, common API across all supported platforms + - only one header and one source file for easy inclusion in programming projects + - MIDI device enumeration + +MIDI input and output functionality are separated into two classes, `RtMidiIn` and `RtMidiOut`. Each class instance supports only a single MIDI connection. RtMidi does not provide timing functionality (i.e., output messages are sent immediately). Input messages are timestamped with delta times in seconds (via a `double` floating point type). MIDI data is passed to the user as raw bytes using an `std::vector`. + +## Windows + +In some cases, for example to use RtMidi with GS Synth, it may be necessary for your program to call `CoInitializeEx` and `CoUninitialize` on entry to and exit from the thread that uses RtMidi. + +## Further reading + +For complete documentation on RtMidi, see the `doc` directory of the distribution or surf to http://www.music.mcgill.ca/~gary/rtmidi/. + +## Legal and ethical + +The RtMidi license is similar to the MIT License, with the added *feature* that modifications be sent to the developer. Please see [LICENSE](LICENSE). diff --git a/extern/rtmidi/RtMidi.cpp b/extern/rtmidi/RtMidi.cpp new file mode 100644 index 00000000..6a1c89e6 --- /dev/null +++ b/extern/rtmidi/RtMidi.cpp @@ -0,0 +1,3934 @@ +/**********************************************************************/ +/*! \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-2021 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 (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 + +// **************************************************************** // +// +// MidiInApi and MidiOutApi subclass prototypes. +// +// **************************************************************** // + +#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) + #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(__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(__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" }, + { "web" , "Web MIDI API" }, + { "dummy" , "Dummy" }, +}; +const unsigned int rtmidi_num_api_names = + sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]); + +// The order here will control the order of RtMidi's API search in +// the constructor. +extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { +#if defined(__MACOSX_CORE__) + RtMidi::MACOSX_CORE, +#endif +#if defined(__LINUX_ALSA__) + RtMidi::LINUX_ALSA, +#endif +#if defined(__UNIX_JACK__) + RtMidi::UNIX_JACK, +#endif +#if defined(__WINDOWS_MM__) + RtMidi::WINDOWS_MM, +#endif +#if defined(__WEB_MIDI_API__) + RtMidi::WEB_MIDI_API, +#endif +#if defined(__RTMIDI_DUMMY__) + RtMidi::RTMIDI_DUMMY, +#endif + RtMidi::UNSPECIFIED, +}; +extern "C" const unsigned int rtmidi_num_compiled_apis = + sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1; +} + +// This is a compile-time check that rtmidi_num_api_names == RtMidi::NUM_APIS. +// If the build breaks here, check that they match. +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +class StaticAssertions { StaticAssertions() { + StaticAssert(); +}}; + +void RtMidi :: getCompiledApi( std::vector &apis ) throw() +{ + apis = std::vector(rtmidi_compiled_apis, + rtmidi_compiled_apis + rtmidi_num_compiled_apis); +} + +std::string RtMidi :: getApiName( RtMidi::Api api ) +{ + if (api < 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(__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(__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(__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(__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 EndpointName( MIDIEndpointRef endpoint, bool isExternal ) +{ + CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); + CFStringRef str; + + // Begin with the endpoint's name. + str = NULL; + MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); + if ( str != NULL ) { + CFStringAppend( result, str ); + CFRelease( str ); + } + + // some MIDI devices have a leading space in endpoint name. trim + CFStringRef space = CFStringCreateWithCString(NULL, " ", kCFStringEncodingUTF8); + CFStringTrim(result, space); + CFRelease(space); + + MIDIEntityRef entity = 0; + MIDIEndpointGetEntity( endpoint, &entity ); + if ( entity == 0 ) + // probably virtual + return result; + + if ( CFStringGetLength( result ) == 0 ) { + // endpoint name has zero length -- try the entity + str = NULL; + MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); + if ( str != NULL ) { + CFStringAppend( result, str ); + CFRelease( str ); + } + } + // now consider the device's name + MIDIDeviceRef device = 0; + MIDIEntityGetDevice( entity, &device ); + if ( device == 0 ) + return result; + + str = NULL; + MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); + if ( CFStringGetLength( result ) == 0 ) { + CFRelease( result ); + return str; + } + if ( str != NULL ) { + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { + CFRelease( result ); + return str; + } else { + if ( CFStringGetLength( str ) == 0 ) { + CFRelease( str ); + return result; + } + // does the entity name already start with the device name? + // (some drivers do this though they shouldn't) + // if so, do not prepend + if ( CFStringCompareWithOptions( result, /* endpoint name */ + str /* device name */, + CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { + // prepend the device name to the entity name + if ( CFStringGetLength( result ) > 0 ) + CFStringInsert( result, 0, CFSTR(" ") ); + + CFStringInsert( result, 0, str ); + } + CFRelease( str ); + } + } + return result; +} + +// This function was submitted by Douglas Casey Tucker and apparently +// derived largely from PortMidi. +static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) +{ + CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); + CFStringRef str; + OSStatus err; + int i; + + // Does the endpoint have connections? + CFDataRef connections = NULL; + int nConnected = 0; + bool anyStrings = false; + err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); + if ( connections != NULL ) { + // It has connections, follow them + // Concatenate the names of all connected devices + nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); + if ( nConnected ) { + const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); + for ( i=0; i= MIDIGetNumberOfSources() ) { + std::ostringstream ost; + ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + return stringName; + } + + portRef = MIDIGetSource( portNumber ); + nameRef = ConnectedEndpointName( portRef ); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); + CFRelease( nameRef ); + + return stringName = name; +} + +//*********************************************************************// +// API: OS-X +// Class Definitions: MidiOutCore +//*********************************************************************// + +MidiOutCore :: MidiOutCore( const std::string &clientName ) + : MidiOutApi() +{ + MidiOutCore::initialize( clientName ); +} + +MidiOutCore :: ~MidiOutCore( void ) +{ + // Close a connection if it exists. + MidiOutCore::closePort(); + + // Cleanup. + CoreMidiData *data = static_cast (apiData_); + 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 = ConnectedEndpointName(portRef); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); + CFRelease( nameRef ); + + return stringName = name; +} + +void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName ) +{ + if ( connected_ ) { + errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + unsigned int nDest = MIDIGetNumberOfDestinations(); + if (nDest < 1) { + errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDest ) { + std::ostringstream ost; + ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast (apiData_); + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); + CFRelease( portNameRef ); + if ( result != noErr ) { + 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 ) 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 ( 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 ( int 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: 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 +#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 ) + pthread_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, &ret ); + 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; +} + +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; +} + +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; +} + +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; +} + +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__ diff --git a/extern/rtmidi/RtMidi.h b/extern/rtmidi/RtMidi.h new file mode 100644 index 00000000..a6f5b794 --- /dev/null +++ b/extern/rtmidi/RtMidi.h @@ -0,0 +1,658 @@ +/**********************************************************************/ +/*! \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-2021 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 "5.0.0" + +#include +#include +#include +#include + + +/************************************************************************/ +/*! \class RtMidiError + \brief Exception handling class for RtMidi. + + The RtMidiError class is quite simple but it does allow errors to be + "caught" by RtMidiError::Type. See the RtMidi documentation to know + which methods can throw an RtMidiError. +*/ +/************************************************************************/ + +class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception +{ + public: + //! Defined RtMidiError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() + : message_(message), type_(type) {} + + //! The destructor. + virtual ~RtMidiError( void ) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType( void ) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage( void ) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char* what( void ) const throw() { return message_.c_str(); } + + protected: + std::string message_; + Type type_; +}; + +//! RtMidi error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + + Note that class behaviour is undefined after a critical error (not + a warning) is reported. + */ +typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData ); + +class MidiApi; + +class RTMIDI_DLL_PUBLIC RtMidi +{ + public: + + 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. */ + NUM_APIS /*!< Number of values in this enum. */ + }; + + //! A static function to determine the current RtMidi version. + static std::string getVersion( void ) throw(); + + //! A static function to determine the available compiled MIDI APIs. + /*! + The values returned in the std::vector can be compared against + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ + static void getCompiledApi( std::vector &apis ) throw(); + + //! Return the name of a specified compiled MIDI API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName( RtMidi::Api api ); + + //! Return the display name of a specified compiled MIDI API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName( RtMidi::Api api ); + + //! Return the compiled MIDI API having the given name. + /*! + A case insensitive comparison will check the specified name + against the list of compiled APIs, and return the one which + matches. On failure, the function returns UNSPECIFIED. + */ + static RtMidi::Api getCompiledApiByName( const std::string &name ); + + //! Pure virtual openPort() function. + virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0; + + //! Pure virtual openVirtualPort() function. + virtual void openVirtualPort( const std::string &portName = std::string( "RtMidi" ) ) = 0; + + //! Pure virtual getPortCount() function. + virtual unsigned int getPortCount() = 0; + + //! Pure virtual getPortName() function. + virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; + + //! Pure virtual closePort() function. + virtual void closePort( void ) = 0; + + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen( void ) const = 0; + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; + + protected: + RtMidi(); + virtual ~RtMidi(); + MidiApi *rtapi_; + + /* 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 occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); + + //! 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 occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); + + protected: + void openMidiApi( RtMidi::Api api, const std::string &clientName ); +}; + + +// **************************************************************** // +// +// MidiInApi / MidiOutApi class declarations. +// +// Subclasses of MidiInApi and MidiOutApi contain all API- and +// OS-specific code necessary to fully implement the RtMidi API. +// +// Note that MidiInApi and MidiOutApi are abstract base classes and +// cannot be explicitly instantiated. RtMidiIn and RtMidiOut will +// create instances of a MidiInApi or MidiOutApi subclass. +// +// **************************************************************** // + +class RTMIDI_DLL_PUBLIC MidiApi +{ + public: + + MidiApi(); + virtual ~MidiApi(); + virtual RtMidi::Api getCurrentApi( void ) = 0; + virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0; + virtual void openVirtualPort( const std::string &portName ) = 0; + virtual void closePort( void ) = 0; + virtual void setClientName( const std::string &clientName ) = 0; + virtual void setPortName( const std::string &portName ) = 0; + + virtual unsigned int getPortCount( void ) = 0; + virtual std::string getPortName( unsigned int portNumber ) = 0; + + inline bool isPortOpen() const { return connected_; } + void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ); + + //! A basic error reporting function for RtMidi classes. + void error( RtMidiError::Type type, std::string errorString ); + +protected: + virtual void initialize( const std::string& clientName ) = 0; + + void *apiData_; + bool connected_; + std::string errorString_; + RtMidiErrorCallback errorCallback_; + bool firstErrorOccurred_; + void *errorCallbackUserData_; + +}; + +class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi +{ + public: + + MidiInApi( unsigned int queueSizeLimit ); + virtual ~MidiInApi( void ); + void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); + void cancelCallback( void ); + virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); + double getMessage( std::vector *message ); + 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/extern/rtmidi/autogen.sh b/extern/rtmidi/autogen.sh new file mode 100755 index 00000000..8bd52e88 --- /dev/null +++ b/extern/rtmidi/autogen.sh @@ -0,0 +1,107 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +DIE=0 + +if test -z "$*"; then + echo "**Warning**: I am going to run \`configure' with arguments for" + echo "developer/maintainer mode. If you wish to pass extra arguments," + echo "(such as --prefix), please specify them on the \`$0'" + echo "command line." + echo "If you wish to run configure yourself, please specify --no-configure." + echo +fi + +(test -f $srcdir/configure.ac) || { + echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" + echo " top-level package directory" + exit 1 +} + +# Make some directories required by automake, if they don't exist +if ! [ -d config ]; then mkdir -v config; fi +if ! [ -d m4 ]; then mkdir -v m4; fi + +if ! autoreconf --version /dev/null 2>&1 +then + +(autoconf --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`autoconf' installed." + echo "Download the appropriate package for your distribution," + echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" + DIE=1 +} + +(grep "^LT_INIT" $srcdir/configure.ac >/dev/null) && { + (libtoolize --version) < /dev/null > /dev/null 2>&1 \ + && LIBTOOLIZE=libtoolize || { + (glibtoolize --version) < /dev/null > /dev/null 2>&1 \ + && LIBTOOLIZE=glibtoolize || { + echo + echo "**Error**: You must have \`libtool' installed." + echo "You can get it from: ftp://ftp.gnu.org/pub/gnu/" + DIE=1 + } + } +} + +(automake --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`automake' installed." + echo "You can get it from: ftp://ftp.gnu.org/pub/gnu/" + DIE=1 + NO_AUTOMAKE=yes +} + + +# if no automake, don't bother testing for aclocal +test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: Missing \`aclocal'. The version of \`automake'" + echo "installed doesn't appear recent enough." + echo "You can get automake from ftp://ftp.gnu.org/pub/gnu/" + DIE=1 +} + +if test "$DIE" -eq 1; then + exit 1 +fi + +case $CC in +xlc ) + am_opt=--include-deps;; +esac + +echo "Running aclocal $aclocalinclude ..." +aclocal $ACLOCAL_FLAGS || exit 1 +echo "Running $LIBTOOLIZE ..." +$LIBTOOLIZE || exit 1 +echo "Running automake --gnu $am_opt ..." +automake --add-missing --gnu $am_opt || exit 1 +echo "Running autoconf ..." +autoconf || exit 1 + +else # autoreconf instead + + echo "Running autoreconf --verbose --install ..." + autoreconf --verbose --install || exit 1 + +fi + +if ( echo "$@" | grep -q -e "--no-configure" ); then + NOCONFIGURE=1 +fi + +conf_flags="--enable-maintainer-mode --enable-debug --disable-silent-rules" + +if test x$NOCONFIGURE = x; then + echo Running $srcdir/configure $conf_flags "$@" ... + $srcdir/configure $conf_flags "$@" \ + && echo Now type \`make\' to compile. || exit 1 +else + echo Skipping configure process. +fi diff --git a/extern/rtmidi/cmake/RtMidi-config.cmake.in b/extern/rtmidi/cmake/RtMidi-config.cmake.in new file mode 100644 index 00000000..57da1755 --- /dev/null +++ b/extern/rtmidi/cmake/RtMidi-config.cmake.in @@ -0,0 +1,20 @@ +#.rst: +# RtMidi +# ------- +# +# The following import targets are created +# +# :: +# +# +# RtMidi::rtmidi +# +# + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +if(NOT TARGET rtmidi) + include("${CMAKE_CURRENT_LIST_DIR}/RtMidiTargets.cmake") +endif() diff --git a/extern/rtmidi/cmake/RtMidiConfigUninstall.cmake.in b/extern/rtmidi/cmake/RtMidiConfigUninstall.cmake.in new file mode 100644 index 00000000..db894b3f --- /dev/null +++ b/extern/rtmidi/cmake/RtMidiConfigUninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if(EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif(NOT "${rm_retval}" STREQUAL 0) + else(EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif(EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/extern/rtmidi/configure.ac b/extern/rtmidi/configure.ac new file mode 100644 index 00000000..960af429 --- /dev/null +++ b/extern/rtmidi/configure.ac @@ -0,0 +1,254 @@ +# Process this file with autoconf to produce a configure script. +AC_INIT(RtMidi, 5.0.0, gary.scavone@mcgill.ca, rtmidi) +AC_CONFIG_AUX_DIR(config) +AC_CONFIG_SRCDIR(RtMidi.cpp) +AC_CONFIG_FILES([rtmidi-config rtmidi.pc Makefile tests/Makefile doc/Makefile doc/doxygen/Doxyfile]) +AM_INIT_AUTOMAKE([1.14 -Wall -Werror foreign subdir-objects]) + +# libtool version: current:revision:age +# +# If the library source code has changed at all since the last update, then +# increment revision (`c:r:a' becomes `c:r+1:a'). +# +# If any interfaces have been added, removed, or changed since the last update, +# increment current, and set revision to 0. +# +# If any interfaces have been added since the last public release, then +# increment age. +# +# If any interfaces have been removed since the last public release, then set +# age to 0. +m4_define([lt_current], 6) +m4_define([lt_revision], 0) +m4_define([lt_age], 0) + +m4_define([lt_version_info], [lt_current:lt_revision:lt_age]) +m4_define([lt_current_minus_age], [m4_eval(lt_current - lt_age)]) + +SO_VERSION=lt_version_info +AC_SUBST(SO_VERSION) +AC_SUBST(LIBS) +AC_SUBST(api) +AC_SUBST(req) +AC_SUBST(req_libs) + +api="" +req="" +req_libs="" + +# Fill GXX with something before test. +GXX="no" + +# if the user did not provide any CXXFLAGS, we can override them +AS_IF([test "x$CXXFLAGS" = "x" ], [override_cxx=yes], [override_cxx=no]) +AS_IF([test "x$CFLAGS" = "x" ], [override_c=yes], [override_c=no]) + +# Check version number coherency between RtMidi.h and configure.ac +AC_MSG_CHECKING([that version numbers are coherent]) +RTMIDI_VERSION=`sed -n 's/#define RTMIDI_VERSION "\(.*\)"/\1/p' $srcdir/RtMidi.h` +AS_IF( + [test "x$RTMIDI_VERSION" != "x$PACKAGE_VERSION"], + [AC_MSG_FAILURE([testing RTMIDI_VERSION==PACKAGE_VERSION failed, check that RtMidi.h defines RTMIDI_VERSION as "$PACKAGE_VERSION" or that the first line of configure.ac has been updated.])]) + +# Enable some nice automake features if they are available +m4_ifdef([AM_MAINTAINER_MODE], [AM_MAINTAINER_MODE]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +# standards version +m4_include([m4/ax_cxx_compile_stdcxx.m4]) +AX_CXX_COMPILE_STDCXX(11, noext, mandatory) + +# configure flags +AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [enable various debugging output])]) +AC_ARG_WITH(jack, [AS_HELP_STRING([--with-jack], [choose JACK server support])]) +AC_ARG_WITH(alsa, [AS_HELP_STRING([--with-alsa], [choose native ALSA sequencer API support (linux only)])]) +AC_ARG_WITH(core, [AS_HELP_STRING([--with-core], [ choose CoreMIDI API support (mac only)])]) +AC_ARG_WITH(winmm, [AS_HELP_STRING([--with-winmm], [ choose Windows MultiMedia (MM) API support (win32 only)])]) +AC_ARG_WITH(winks, [AS_HELP_STRING([--with-winks], [ choose kernel streaming support (win32 only)])]) +AC_ARG_WITH(webmidi, [AS_HELP_STRING([--with-webmidi], [ choose Web MIDI support])]) + + +# Checks for programs. +AC_PROG_CXX(g++ CC c++ cxx) +AM_PROG_AR +AC_PATH_PROG(AR, ar, no) +AS_IF([test "x$AR" = "xno"], [ + AC_MSG_ERROR([Could not find ar - needed to create a library]) +]) + +LT_INIT([win32-dll]) +AC_CONFIG_MACRO_DIR([m4]) + +# Checks for header files. +AC_HEADER_STDC +#AC_CHECK_HEADERS(sys/ioctl.h unistd.h) + +# Check for POSIX semaphore support +AC_CHECK_HEADER([semaphore.h], [ + AC_CHECK_LIB(pthread, sem_init, + AC_DEFINE([HAVE_SEMAPHORE],[1],[Define to 1 if you have POSIX semaphore support on your system.]), + AC_MSG_WARN([POSIX semaphore support not found; data may be lost on closePort])) +]) + + +# check for debug +AC_MSG_CHECKING(whether to compile debug version) +debugflags="" +object_path=Release +AS_CASE([${enable_debug}], + [ yes ], [ + AC_MSG_RESULT([yes]) + CPPFLAGS="-D__RTMIDI_DEBUG ${CPPFLAGS}" + debugflags="${debugflags} -g" + object_path=Debug + ], + [ no ], [ + AC_MSG_RESULT([no!]) + debugflags="${debugflags} -O3" + ], [ + AC_MSG_RESULT([no]) + ]) +# For debugging and optimization ... overwrite default because it has both -g and -O2 +AS_IF([test "x$debugflags" != x], + AS_IF([test "x$override_cxx" = "xyes" ], CXXFLAGS="$CXXFLAGS $debugflags", CXXFLAGS="$debugflags $CXXFLAGS") + AS_IF([test "x$override_c" = "xyes" ], CFLAGS="$CFLAGS $debugflags", CFLAGS="$debugflags $CFLAGS") + ) + +# Check compiler and use -Wall if gnu. +AS_IF([test "x$GXX" = "xyes"], [ + CXXFLAGS="-Wall -Wextra ${CXXFLAGS}" +]) + +# Checks for doxygen +AC_CHECK_PROG( DOXYGEN, [doxygen], [doxygen] ) +AM_CONDITIONAL( MAKE_DOC, [test "x${DOXYGEN}" != x] ) + +# Copy doc files to build dir if necessary +AC_CONFIG_LINKS( [doc/doxygen/footer.html:doc/doxygen/footer.html] ) +AC_CONFIG_LINKS( [doc/doxygen/header.html:doc/doxygen/header.html] ) +AC_CONFIG_LINKS( [doc/doxygen/tutorial.txt:doc/doxygen/tutorial.txt] ) +AC_CONFIG_LINKS( [doc/images/ccrma.gif:doc/images/ccrma.gif] ) +AC_CONFIG_LINKS( [doc/images/mcgill.gif:doc/images/mcgill.gif] ) + +# Checks for package options and external software +AC_CANONICAL_HOST + +# Aggregate options into a single string. +AS_IF([test "x$with_jack" = "xyes"], [systems="$systems jack"]) +AS_IF([test "x$with_alsa" = "xyes"], [systems="$systems alsa"]) +AS_IF([test "x$with_core" = "xyes"], [systems="$systems core"]) +AS_IF([test "x$with_winmm" = "xyes"], [systems="$systems winmm"]) +AS_IF([test "x$with_winks" = "xyes"], [systems="$systems winks"]) +AS_IF([test "x$with_webmidi" = "xyes"], [systems="$systems webmidi"]) +AS_IF([test "x$with_dummy" = "xyes"], [systems="$systems dummy"]) +required=" $systems " + +# If none, assign defaults if any are known for this OS. +# User must specified with-* options for any unknown OS. +AS_IF([test "x$systems" = "x"], + AS_CASE([$host], + [*-*-linux*], [systems="alsa jack"], + [*-*-gnu], [systems="jack"], + [*-*-kfreebsd*-gnu], [systems="jack"], + [*-apple*], [systems="core jack"], + [*-mingw32*], [systems="winmm winks jack"], + [*-mingw64*], [systems="winmm winks jack"] + )) + +# If any were specifically requested disabled, do it. +AS_IF([test "x$with_jack" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v jack`]) +AS_IF([test "x$with_alsa" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v alsa`]) +AS_IF([test "x$with_winmm" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v winmm`]) +AS_IF([test "x$with_winks" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v winks`]) +AS_IF([test "x$with_core" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v core`]) +AS_IF([test "x$with_webmidi" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v webmidi`]) +AS_IF([test "x$with_dummy" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v dummy`]) +systems=" `echo $systems|tr \\\\n ' '` " + +# For each MIDI system, check if it is selected and found. +# Note: Order specified above is not necessarily respected. However, +# *actual* priority is set at run-time, see RtMidiIn::openMidiApi and RtMidiOut::openMidiApi. +# One AS_CASE per system, since they are not mutually-exclusive. + +AS_CASE(["$systems"], [*" jack "*], [ + AC_CHECK_LIB(jack, jack_client_open, + [api="$api -D__UNIX_JACK__" + req="$req jack" + need_pthread=yes + found="$found Jack" + LIBS="-ljack $LIBS"], + AS_CASE(["$required"], [*" jack "*], + AC_MSG_ERROR([JACK support requires the jack library!]))) + AC_CHECK_LIB(jack, jack_port_rename, AC_DEFINE(JACK_HAS_PORT_RENAME), ) +]) + +AS_CASE(["$systems"], [*" alsa "*], [ + AC_CHECK_LIB(asound, snd_seq_open, + [api="$api -D__LINUX_ALSA__" + req="$req alsa" + need_pthread=yes + found="$found ALSA" + LIBS="-lasound $LIBS"], + AS_CASE(["$required"], [*" alsa "*], + AC_MSG_ERROR([ALSA support requires the asound library!]))) +]) + +AS_CASE(["$systems"], [*" core "*], [ + AC_CHECK_HEADER(CoreMIDI/CoreMIDI.h, + [api="$api -D__MACOSX_CORE__" + req_libs="$req_libs -framework CoreMIDI -framework CoreAudio -framework CoreFoundation" + need_pthread=yes + found="$found CoreMIDI", + LIBS="$LIBS -framework CoreMIDI -framework CoreFoundation -framework CoreAudio"], + AS_CASE(["$required"], [*" core "*], + AC_MSG_ERROR([CoreMIDI header files not found!]))) +]) + +AS_CASE(["$systems"], [*" winmm "*], [ + AC_CHECK_HEADERS(windows.h mmsystem.h, + [api="$api -D__WINDOWS_MM__" + CPPFLAGS="-I$srcdir/include $CPPFLAGS" + need_ole32=yes + found="$found WinMM" + api="$api -D__WINDOWS_MM__" + LIBS="-lwinmm ${LIBS}"]) +]) + +AS_CASE(["$systems"], [*" winks "*], [ + AC_CHECK_HEADERS(windows.h audioclient.h avrt.h mmdeviceapi.h, + [api="$api -D__WINDOWS_WINKS__" + CPPFLAGS="-I$srcdir/include $CPPFLAGS" + need_ole32=yes + found="$found kernel-streaming" + api="$api -D__WINDOWS_WINKS__" + LIBS="-lsetupapi -lksuser ${LIBS}"]) +]) + +AS_CASE(["$systems"], [*" webmidi "*], [ + AC_CHECK_HEADERS(emscripten.h, + [api="$api -D__WEB_MIDI_API__" + found="$found Web MIDI"]) +]) + +AS_IF([test -n "$need_ole32"], [LIBS="-lole32 $LIBS"]) + +AS_IF([test -n "$need_pthread"],[ + AC_MSG_CHECKING([for pthread]) + AC_CHECK_LIB(pthread, pthread_create, , + AC_MSG_ERROR([RtAudio requires the pthread library!]))]) + +AC_MSG_CHECKING([for MIDI API]) + +# Error case: no known realtime systems found. +AS_IF([test x"$api" = "x"], [ + AC_MSG_RESULT([none]) + AC_MSG_ERROR([No known system type found for MIDI support!]) +], [ + AC_MSG_RESULT([$found]) +]) + +CPPFLAGS="$CPPFLAGS $api" + +AC_OUTPUT + +chmod oug+x rtmidi-config diff --git a/extern/rtmidi/contrib/go/rtmidi/rtmidi.go b/extern/rtmidi/contrib/go/rtmidi/rtmidi.go new file mode 100644 index 00000000..f98402bb --- /dev/null +++ b/extern/rtmidi/contrib/go/rtmidi/rtmidi.go @@ -0,0 +1,355 @@ +package rtmidi + +/* +#cgo CXXFLAGS: -g +#cgo LDFLAGS: -g + +#cgo linux CXXFLAGS: -D__LINUX_ALSA__ +#cgo linux LDFLAGS: -lasound -pthread +#cgo windows CXXFLAGS: -D__WINDOWS_MM__ +#cgo windows LDFLAGS: -luuid -lksuser -lwinmm -lole32 +#cgo darwin CXXFLAGS: -D__MACOSX_CORE__ +#cgo darwin LDFLAGS: -framework CoreServices -framework CoreAudio -framework CoreMIDI -framework CoreFoundation + +#include +#include +#include "rtmidi_stub.h" + +extern void goMIDIInCallback(double ts, unsigned char *msg, size_t msgsz, void *arg); + +static inline void midiInCallback(double ts, const unsigned char *msg, size_t msgsz, void *arg) { + goMIDIInCallback(ts, (unsigned char*) msg, msgsz, arg); +} + +static inline void cgoSetCallback(RtMidiPtr in, int cb_id) { + rtmidi_in_set_callback(in, midiInCallback, (void*)(uintptr_t) cb_id); +} +*/ +import "C" +import ( + "errors" + "sync" + "unsafe" +) + +// API is an enumeration of possible MIDI API specifiers. +type API C.enum_RtMidiApi + +const ( + // APIUnspecified searches for a working compiled API. + APIUnspecified API = C.RTMIDI_API_UNSPECIFIED + // APIMacOSXCore uses Macintosh OS-X CoreMIDI API. + APIMacOSXCore = C.RTMIDI_API_MACOSX_CORE + // APILinuxALSA uses the Advanced Linux Sound Architecture API. + APILinuxALSA = C.RTMIDI_API_LINUX_ALSA + // APIUnixJack uses the JACK Low-Latency MIDI Server API. + APIUnixJack = C.RTMIDI_API_UNIX_JACK + // APIWindowsMM uses the Microsoft Multimedia MIDI API. + APIWindowsMM = C.RTMIDI_API_WINDOWS_MM + // APIDummy is a compilable but non-functional API. + APIDummy = C.RTMIDI_API_RTMIDI_DUMMY +) + +func (api API) String() string { + switch api { + case APIUnspecified: + return "unspecified" + case APILinuxALSA: + return "alsa" + case APIUnixJack: + return "jack" + case APIMacOSXCore: + return "coreaudio" + case APIWindowsMM: + return "winmm" + case APIDummy: + return "dummy" + } + return "?" +} + +// CompiledAPI determines the available compiled MIDI APIs. +func CompiledAPI() (apis []API) { + n := C.rtmidi_get_compiled_api(nil, 0) + capis := make([]C.enum_RtMidiApi, n, n) + C.rtmidi_get_compiled_api(&capis[0], C.uint(n)) + for _, capi := range capis { + apis = append(apis, API(capi)) + } + return apis +} + +// MIDI interface provides a common, platform-independent API for realtime MIDI +// device enumeration and handling MIDI ports. +type MIDI interface { + OpenPort(port int, name string) error + OpenVirtualPort(name string) error + Close() error + PortCount() (int, error) + PortName(port int) (string, error) +} + +// MIDIIn interface 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 Message() +// method 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. +type MIDIIn interface { + MIDI + API() (API, error) + IgnoreTypes(midiSysex bool, midiTime bool, midiSense bool) error + SetCallback(func(MIDIIn, []byte, float64)) error + CancelCallback() error + Message() ([]byte, float64, error) + Destroy() +} + +// MIDIOut interface 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. +type MIDIOut interface { + MIDI + API() (API, error) + SendMessage([]byte) error + Destroy() +} + +type midi struct { + midi C.RtMidiPtr +} + +func (m *midi) OpenPort(port int, name string) error { + p := C.CString(name) + defer C.free(unsafe.Pointer(p)) + C.rtmidi_open_port(m.midi, C.uint(port), p) + if !m.midi.ok { + return errors.New(C.GoString(m.midi.msg)) + } + return nil +} + +func (m *midi) OpenVirtualPort(name string) error { + p := C.CString(name) + defer C.free(unsafe.Pointer(p)) + C.rtmidi_open_virtual_port(m.midi, p) + if !m.midi.ok { + return errors.New(C.GoString(m.midi.msg)) + } + return nil +} + +func (m *midi) PortName(port int) (string, error) { + p := C.rtmidi_get_port_name(m.midi, C.uint(port)) + if !m.midi.ok { + return "", errors.New(C.GoString(m.midi.msg)) + } + defer C.free(unsafe.Pointer(p)) + return C.GoString(p), nil +} + +func (m *midi) PortCount() (int, error) { + n := C.rtmidi_get_port_count(m.midi) + if !m.midi.ok { + return 0, errors.New(C.GoString(m.midi.msg)) + } + return int(n), nil +} + +func (m *midi) Close() error { + C.rtmidi_close_port(C.RtMidiPtr(m.midi)) + if !m.midi.ok { + return errors.New(C.GoString(m.midi.msg)) + } + return nil +} + +type midiIn struct { + midi + in C.RtMidiInPtr + cb func(MIDIIn, []byte, float64) +} + +type midiOut struct { + midi + out C.RtMidiOutPtr +} + +// NewMIDIInDefault opens a default MIDIIn port. +func NewMIDIInDefault() (MIDIIn, error) { + in := C.rtmidi_in_create_default() + if !in.ok { + defer C.rtmidi_in_free(in) + return nil, errors.New(C.GoString(in.msg)) + } + return &midiIn{in: in, midi: midi{midi: C.RtMidiPtr(in)}}, nil +} + +// NewMIDIIn opens a single MIDIIn port using the given API. One can provide a +// custom port name and a desired queue size for the incomming MIDI messages. +func NewMIDIIn(api API, name string, queueSize int) (MIDIIn, error) { + p := C.CString(name) + defer C.free(unsafe.Pointer(p)) + in := C.rtmidi_in_create(C.enum_RtMidiApi(api), p, C.uint(queueSize)) + if !in.ok { + defer C.rtmidi_in_free(in) + return nil, errors.New(C.GoString(in.msg)) + } + return &midiIn{in: in, midi: midi{midi: C.RtMidiPtr(in)}}, nil +} + +func (m *midiIn) API() (API, error) { + api := C.rtmidi_in_get_current_api(m.in) + if !m.in.ok { + return APIUnspecified, errors.New(C.GoString(m.in.msg)) + } + return API(api), nil +} + +func (m *midiIn) Close() error { + unregisterMIDIIn(m) + if err := m.midi.Close(); err != nil { + return err + } + C.rtmidi_in_free(m.in) + return nil +} + +func (m *midiIn) IgnoreTypes(midiSysex bool, midiTime bool, midiSense bool) error { + C.rtmidi_in_ignore_types(m.in, C._Bool(midiSysex), C._Bool(midiTime), C._Bool(midiSense)) + if !m.in.ok { + return errors.New(C.GoString(m.in.msg)) + } + return nil +} + +var ( + mu sync.Mutex + inputs = map[int]*midiIn{} +) + +func registerMIDIIn(m *midiIn) int { + mu.Lock() + defer mu.Unlock() + for i := 0; ; i++ { + if _, ok := inputs[i]; !ok { + inputs[i] = m + return i + } + } +} + +func unregisterMIDIIn(m *midiIn) { + mu.Lock() + defer mu.Unlock() + for i := 0; i < len(inputs); i++ { + if inputs[i] == m { + delete(inputs, i) + return + } + } +} + +func findMIDIIn(k int) *midiIn { + mu.Lock() + defer mu.Unlock() + return inputs[k] +} + +//export goMIDIInCallback +func goMIDIInCallback(ts C.double, msg *C.uchar, msgsz C.size_t, arg unsafe.Pointer) { + k := int(uintptr(arg)) + m := findMIDIIn(k) + m.cb(m, C.GoBytes(unsafe.Pointer(msg), C.int(msgsz)), float64(ts)) +} + +func (m *midiIn) SetCallback(cb func(MIDIIn, []byte, float64)) error { + k := registerMIDIIn(m) + m.cb = cb + C.cgoSetCallback(m.in, C.int(k)) + if !m.in.ok { + return errors.New(C.GoString(m.in.msg)) + } + return nil +} + +func (m *midiIn) CancelCallback() error { + unregisterMIDIIn(m) + C.rtmidi_in_cancel_callback(m.in) + if !m.in.ok { + return errors.New(C.GoString(m.in.msg)) + } + return nil +} + +func (m *midiIn) Message() ([]byte, float64, error) { + msg := make([]C.uchar, 64*1024, 64*1024) + sz := C.size_t(len(msg)) + r := C.rtmidi_in_get_message(m.in, &msg[0], &sz) + if !m.in.ok { + return nil, 0, errors.New(C.GoString(m.in.msg)) + } + b := make([]byte, int(sz), int(sz)) + for i, c := range msg[:sz] { + b[i] = byte(c) + } + return b, float64(r), nil +} + +func (m *midiIn) Destroy() { + C.rtmidi_in_free(m.in) +} + +// NewMIDIOutDefault opens a default MIDIOut port. +func NewMIDIOutDefault() (MIDIOut, error) { + out := C.rtmidi_out_create_default() + if !out.ok { + defer C.rtmidi_out_free(out) + return nil, errors.New(C.GoString(out.msg)) + } + return &midiOut{out: out, midi: midi{midi: C.RtMidiPtr(out)}}, nil +} + +// NewMIDIOut opens a single MIDIIn port using the given API with the given port name. +func NewMIDIOut(api API, name string) (MIDIOut, error) { + p := C.CString(name) + defer C.free(unsafe.Pointer(p)) + out := C.rtmidi_out_create(C.enum_RtMidiApi(api), p) + if !out.ok { + defer C.rtmidi_out_free(out) + return nil, errors.New(C.GoString(out.msg)) + } + return &midiOut{out: out, midi: midi{midi: C.RtMidiPtr(out)}}, nil +} + +func (m *midiOut) API() (API, error) { + api := C.rtmidi_out_get_current_api(m.out) + if !m.out.ok { + return APIUnspecified, errors.New(C.GoString(m.out.msg)) + } + return API(api), nil +} + +func (m *midiOut) Close() error { + if err := m.midi.Close(); err != nil { + return err + } + C.rtmidi_out_free(m.out) + return nil +} + +func (m *midiOut) SendMessage(b []byte) error { + p := C.CBytes(b) + defer C.free(unsafe.Pointer(p)) + C.rtmidi_out_send_message(m.out, (*C.uchar)(p), C.int(len(b))) + if !m.out.ok { + return errors.New(C.GoString(m.out.msg)) + } + return nil +} + +func (m *midiOut) Destroy() { + C.rtmidi_out_free(m.out) +} diff --git a/extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.cpp b/extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.cpp new file mode 100644 index 00000000..7a5b6924 --- /dev/null +++ b/extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.cpp @@ -0,0 +1,4 @@ +#include "../../../RtMidi.h" + +#include "../../../RtMidi.cpp" +#include "../../../rtmidi_c.cpp" diff --git a/extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.h b/extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.h new file mode 100644 index 00000000..8a53d4c0 --- /dev/null +++ b/extern/rtmidi/contrib/go/rtmidi/rtmidi_stub.h @@ -0,0 +1 @@ +#include "../../../rtmidi_c.h" diff --git a/extern/rtmidi/contrib/go/rtmidi/rtmidi_test.go b/extern/rtmidi/contrib/go/rtmidi/rtmidi_test.go new file mode 100644 index 00000000..8079fb4f --- /dev/null +++ b/extern/rtmidi/contrib/go/rtmidi/rtmidi_test.go @@ -0,0 +1,46 @@ +package rtmidi + +import ( + "log" +) + +func ExampleCompiledAPI() { + for _, api := range CompiledAPI() { + log.Println("Compiled API: ", api) + } +} + +func ExampleMIDIIn_Message() { + in, err := NewMIDIInDefault() + if err != nil { + log.Fatal(err) + } + defer in.Destroy() + if err := in.OpenPort(0, "RtMidi"); err != nil { + log.Fatal(err) + } + defer in.Close() + + for { + m, t, err := in.Message() + if len(m) > 0 { + log.Println(m, t, err) + } + } +} + +func ExampleMIDIIn_SetCallback() { + in, err := NewMIDIInDefault() + if err != nil { + log.Fatal(err) + } + defer in.Destroy() + if err := in.OpenPort(0, "RtMidi"); err != nil { + log.Fatal(err) + } + defer in.Close() + in.SetCallback(func(m MIDIIn, msg []byte, t float64) { + log.Println(msg, t) + }) + <-make(chan struct{}) +} diff --git a/extern/rtmidi/doc/Makefile.am b/extern/rtmidi/doc/Makefile.am new file mode 100644 index 00000000..f22d2a3d --- /dev/null +++ b/extern/rtmidi/doc/Makefile.am @@ -0,0 +1,33 @@ + +MAINTAINERCLEANFILES=Makefile.in + +CLEANFILES=doxygen-build.stamp + +DOX=Doxyfile + +EXTRA_DIST=html release.txt + +INSTIMAGES=html/doxygen.png + +DOC_STAMPS=doxygen-build.stamp + +DOC_DIR=$(HTML_DIR) + +all-local: doxygen-build.stamp + +doxygen-build.stamp: doxygen/$(DOX) $(top_srcdir)/RtMidi.h $(top_srcdir)/rtmidi_c.h + @echo '*** Running doxygen ***' + cd doxygen; $(DOXYGEN) $(DOX) + touch doxygen-build.stamp + +clean-local: + rm -f *~ *.bak $(DOC_STAMPS) || true + if test -d html; then rm -fr html; fi + if test -d latex; then rm -fr latex; fi + if test -d man; then rm -fr man; fi + +distclean-local: clean + rm -f *.stamp || true + if test -d html; then rm -rf html; fi + +html-local: $(DOC_STAMPS) diff --git a/extern/rtmidi/doc/doxygen/Doxyfile.in b/extern/rtmidi/doc/doxygen/Doxyfile.in new file mode 100644 index 00000000..716aa4e3 --- /dev/null +++ b/extern/rtmidi/doc/doxygen/Doxyfile.in @@ -0,0 +1,1865 @@ +# Doxyfile 1.8.3.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = RtMidi + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented classes, +# or namespaces to their corresponding documentation. Such a link can be +# prevented in individual cases by by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES (the +# default) will make doxygen replace the get and set methods by a property in +# the documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = YES + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if section-label ... \endif +# and \cond section-label ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. Do not use +# file names with spaces, bibtex cannot handle them. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = tutorial.txt \ + @top_srcdir@/RtMidi.h \ + @top_srcdir@/rtmidi_c.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = samples + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MD_FILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page (index.html). +# This can be useful if you have a project on for instance GitHub and want reuse +# the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = ../html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = header.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# thA MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and +# SVG. The default value is HTML-CSS, which is slower, but has the best +# compatibility. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. +# There are two flavours of web server based search depending on the +# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for +# searching and an index file used by the script. When EXTERNAL_SEARCH is +# enabled the indexing and searching needs to be provided by external tools. +# See the manual for details. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain +# the search results. Doxygen ships with an example indexer (doxyindexer) and +# search engine (doxysearch.cgi) which are based on the open source search engine +# library Xapian. See the manual for configuration details. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will returned the search results when EXTERNAL_SEARCH is enabled. +# Doxygen ships with an example search engine (doxysearch) which is based on +# the open source search engine library Xapian. See the manual for configuration +# details. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH AND EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id +# of to a relative location where the documentation can be found. +# The format is: EXTRA_SEARCH_MAPPINGS = id1=loc1 id2=loc2 ... + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = __MACOSX_CORE__ \ + __WINDOWS_MM__ \ + __UNIX_JACK__ \ + __LINUX_ALSA__ \ + __WINDOWS_KS__ + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = /Applications/Doxygen.app/Contents/Resources/ + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = /Applications/Doxygen.app/Contents/Resources/ + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/extern/rtmidi/doc/doxygen/footer.html b/extern/rtmidi/doc/doxygen/footer.html new file mode 100644 index 00000000..5fcb1566 --- /dev/null +++ b/extern/rtmidi/doc/doxygen/footer.html @@ -0,0 +1,9 @@ +
+ + + +
©2003-2021 Gary P. Scavone, McGill University. All Rights Reserved.
+ Maintained by Gary P. Scavone, gary at music.mcgill.ca
+ + + diff --git a/extern/rtmidi/doc/doxygen/header.html b/extern/rtmidi/doc/doxygen/header.html new file mode 100644 index 00000000..cd1ddc22 --- /dev/null +++ b/extern/rtmidi/doc/doxygen/header.html @@ -0,0 +1,9 @@ + + +The RtMidi Tutorial + + + +
+Tutorial   Class/Enum List   File List   Compound Members   C interface  
+
diff --git a/extern/rtmidi/doc/doxygen/samples/getting_started.cpp b/extern/rtmidi/doc/doxygen/samples/getting_started.cpp new file mode 100644 index 00000000..d6f63771 --- /dev/null +++ b/extern/rtmidi/doc/doxygen/samples/getting_started.cpp @@ -0,0 +1,11 @@ +#include "RtMidi.h" + +int main() { + try { + RtMidiIn midiin; + } catch (RtMidiError &error) { + // Handle the exception here + error.printMessage(); + } + return 0; +} diff --git a/extern/rtmidi/doc/doxygen/tutorial.txt b/extern/rtmidi/doc/doxygen/tutorial.txt new file mode 100644 index 00000000..014da482 --- /dev/null +++ b/extern/rtmidi/doc/doxygen/tutorial.txt @@ -0,0 +1,484 @@ +/*! \mainpage The RtMidi Tutorial + +
\ref intro    \ref download    \ref start    \ref error    \ref probing    \ref output    \ref input    \ref virtual    \ref compiling    \ref debug    \ref multi    \ref apinotes    \ref acknowledge    \ref license
+ +\section intro Introduction + +RtMidi is a set of C++ classes (RtMidiIn, RtMidiOut and API-specific classes) that provides a common API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMIDI & JACK), and Windows (Multimedia Library) operating systems. RtMidi significantly simplifies the process of interacting with computer MIDI hardware and software. It was designed with the following goals: + +- object oriented C++ design +- simple, common API across all supported platforms +- only one header and one source file for easy inclusion in programming projects +- MIDI device enumeration + +Where applicable, multiple API support can be compiled and a particular API specified when creating an RtAudio instance. + +MIDI input and output functionality are separated into two classes, RtMidiIn and RtMidiOut. Each class instance supports only a single MIDI connection. RtMidi does not provide timing functionality (i.e., output messages are sent immediately). Input messages are timestamped with delta times in seconds (via a \c double floating point type). MIDI data is passed to the user as raw bytes using an std::vector. + +\section whatsnew What's New (Version 5.0.0) + +The version number has been bumped to 5.0.0 because a new API (Web MIDI) was added, as well as at least one new function. Changes in this release include: + +- see git history for complete list of changes +- new Web MIDI API +- iOS support +- new setBufferSize() function +- fixes for WinMM, Jack +- added C API test program +- now requiring C++11 +- build system updates +- midiprobe probes all compiled APIs +- various build system updates and code efficiencies + +\section download Download + +Latest Release (16 November 2021): Version 5.0.0 + +\section start Getting Started + +The first thing that must be done when using RtMidi is to create an instance of the RtMidiIn or RtMidiOut subclasses. RtMidi is an abstract base class, which itself cannot be instantiated. Each default constructor attempts to establish any necessary "connections" with the underlying MIDI system. RtMidi uses C++ exceptions to report errors, necessitating try/catch blocks around many member functions. An RtMidiError can be thrown during instantiation in some circumstances. A warning message may also be reported if no MIDI devices are found during instantiation. The RtMidi classes have been designed to work with "hot pluggable" or virtual (software) MIDI devices, making it possible to connect to MIDI devices that may not have been present when the classes were instantiated. The following code example demonstrates default object construction and destruction: + +\include getting_started.cpp + +Obviously, this example doesn't demonstrate any of the real functionality of RtMidi. However, all uses of RtMidi must begin with construction and must end with class destruction. Further, it is necessary that all class methods that can throw a C++ exception be called within a try/catch block. + + +\section error Error Handling + +RtMidi uses a C++ exception handler called RtMidiError, which is +declared and defined in RtMidi.h. The RtMidiError class is quite +simple but it does allow errors to be "caught" by RtMidiError::Type. +Many RtMidi methods can "throw" an RtMidiError, most typically if a +driver error occurs or an invalid function argument is specified. +There are a number of cases within RtMidi where warning messages may +be displayed but an exception is not thrown. A client error callback +function can be specified (via the RtMidi::setErrorCallback function) +that is invoked when an error occurs. By default, error messages are +not automatically displayed in RtMidi unless the preprocessor +definition __RTMIDI_DEBUG__ is defined during compilation. Messages +associated with caught exceptions can be displayed with, for example, +the RtMidiError::printMessage() function. + + +\section probing Probing Ports / Devices + +A client generally must query the available MIDI ports before deciding which to use. The following example outlines how this can be done. + +\code +// midiprobe.cpp + +#include +#include +#include "RtMidi.h" + +int main() +{ + RtMidiIn *midiin = 0; + RtMidiOut *midiout = 0; + + // RtMidiIn constructor + try { + midiin = new RtMidiIn(); + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + // Check inputs. + unsigned int nPorts = midiin->getPortCount(); + std::cout << "\nThere are " << nPorts << " MIDI input sources available.\n"; + std::string portName; + for ( unsigned int i=0; igetPortName(i); + } + catch ( RtMidiError &error ) { + error.printMessage(); + goto cleanup; + } + std::cout << " Input Port #" << i+1 << ": " << portName << '\n'; + } + + // RtMidiOut constructor + try { + midiout = new RtMidiOut(); + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + // Check outputs. + nPorts = midiout->getPortCount(); + std::cout << "\nThere are " << nPorts << " MIDI output ports available.\n"; + for ( unsigned int i=0; igetPortName(i); + } + catch (RtMidiError &error) { + error.printMessage(); + goto cleanup; + } + std::cout << " Output Port #" << i+1 << ": " << portName << '\n'; + } + std::cout << '\n'; + + // Clean up + cleanup: + delete midiin; + delete midiout; + + return 0; +} +\endcode + +Note that the port enumeration is system specific and will change if any devices are unplugged or plugged (or a new virtual port opened or closed) by the user. Thus, the port numbers should be verified immediately before opening a port. As well, if a user unplugs a device (or closes a virtual port) while a port connection exists to that device/port, a MIDI system error will be generated. + +\section output MIDI Output + +The RtMidiOut class provides simple functionality to immediately send messages over a MIDI connection. No timing functionality is provided. Note that there is an overloaded RtMidiOut::sendMessage() function that does not use std::vectors. + +In the following example, we omit necessary error checking and details regarding OS-dependent sleep functions. For a complete example, see the \c midiout.cpp program in the \c tests directory. + +\code +// midiout.cpp + +#include +#include +#include "RtMidi.h" + +int main() +{ + RtMidiOut *midiout = new RtMidiOut(); + std::vector message; + + // Check available ports. + unsigned int nPorts = midiout->getPortCount(); + if ( nPorts == 0 ) { + std::cout << "No ports available!\n"; + goto cleanup; + } + + // Open first available port. + midiout->openPort( 0 ); + + // Send out a series of MIDI messages. + + // Program change: 192, 5 + message.push_back( 192 ); + message.push_back( 5 ); + midiout->sendMessage( &message ); + + // Control Change: 176, 7, 100 (volume) + message[0] = 176; + message[1] = 7; + message.push_back( 100 ); + midiout->sendMessage( &message ); + + // Note On: 144, 64, 90 + message[0] = 144; + message[1] = 64; + message[2] = 90; + midiout->sendMessage( &message ); + + SLEEP( 500 ); // Platform-dependent ... see example in tests directory. + + // Note Off: 128, 64, 40 + message[0] = 128; + message[1] = 64; + message[2] = 40; + midiout->sendMessage( &message ); + + // Clean up + cleanup: + delete midiout; + + return 0; +} +\endcode + + +\section input MIDI Input + +The RtMidiIn class uses an internal callback function or thread to receive incoming MIDI messages from a port or device. These messages are then either queued and read by the user via calls to the RtMidiIn::getMessage() function or immediately passed to a user-specified callback function (which must be "registered" using the RtMidiIn::setCallback() function). Note that if you have multiple instances of RtMidiIn, each may have its own thread. We'll provide examples of both usages. + +The RtMidiIn class provides the RtMidiIn::ignoreTypes() function to specify that certain MIDI message types be ignored. By default, system exclusive, timing, and active sensing messages are ignored. + +\subsection qmidiin Queued MIDI Input + +The RtMidiIn::getMessage() function does not block. If a MIDI message is available in the queue, it is copied to the user-provided \c std::vector container. When no MIDI message is available, the function returns an empty container. The default maximum MIDI queue size is 1024 messages. This value may be modified with the RtMidiIn::setQueueSizeLimit() function. If the maximum queue size limit is reached, subsequent incoming MIDI messages are discarded until the queue size is reduced. + +In the following example, we omit some necessary error checking and details regarding OS-dependent sleep functions. For a more complete example, see the \c qmidiin.cpp program in the \c tests directory. + +\code +// qmidiin.cpp + +#include +#include +#include +#include "RtMidi.h" + +bool done; +static void finish(int ignore){ done = true; } + +int main() +{ + RtMidiIn *midiin = new RtMidiIn(); + std::vector message; + int nBytes, i; + double stamp; + + // Check available ports. + unsigned int nPorts = midiin->getPortCount(); + if ( nPorts == 0 ) { + std::cout << "No ports available!\n"; + goto cleanup; + } + midiin->openPort( 0 ); + + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, false, false ); + + // Install an interrupt handler function. + done = false; + (void) signal(SIGINT, finish); + + // Periodically check input queue. + std::cout << "Reading MIDI from port ... quit with Ctrl-C.\n"; + while ( !done ) { + stamp = midiin->getMessage( &message ); + nBytes = message.size(); + for ( i=0; i 0 ) + std::cout << "stamp = " << stamp << std::endl; + + // Sleep for 10 milliseconds ... platform-dependent. + SLEEP( 10 ); + } + + // Clean up + cleanup: + delete midiin; + + return 0; +} +\endcode + +\subsection cmidiin MIDI Input with User Callback + +When set, a user-provided callback function will be invoked after the input of a complete MIDI message. It is possible to provide a pointer to user data that can be accessed in the callback function (not shown here). It is necessary to set the callback function immediately after opening the port to avoid having incoming messages written to the queue (which is not emptied when a callback function is set). If you are worried about this happening, you can check the queue using the RtMidi::getMessage() function to verify it is empty (after the callback function is set). + +In the following example, we omit some necessary error checking. For a more complete example, see the \c cmidiin.cpp program in the \c tests directory. + +\code +// cmidiin.cpp + +#include +#include +#include "RtMidi.h" + +void mycallback( double deltatime, std::vector< unsigned char > *message, void *userData ) +{ + unsigned int nBytes = message->size(); + for ( unsigned int i=0; i 0 ) + std::cout << "stamp = " << deltatime << std::endl; +} + +int main() +{ + RtMidiIn *midiin = new RtMidiIn(); + + // Check available ports. + unsigned int nPorts = midiin->getPortCount(); + if ( nPorts == 0 ) { + std::cout << "No ports available!\n"; + goto cleanup; + } + + midiin->openPort( 0 ); + + // Set our callback function. This should be done immediately after + // opening the port to avoid having incoming messages written to the + // queue. + midiin->setCallback( &mycallback ); + + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, false, false ); + + std::cout << "\nReading MIDI input ... press to quit.\n"; + char input; + std::cin.get(input); + + // Clean up + cleanup: + delete midiin; + + return 0; +} +\endcode + +\section virtual Virtual Ports + +The Linux ALSA, Macintosh CoreMIDI and JACK APIs allow for the establishment of virtual input and output MIDI ports to which other software clients can connect. RtMidi incorporates this functionality with the RtMidiIn::openVirtualPort() and RtMidiOut::openVirtualPort() functions. Any messages sent with the RtMidiOut::sendMessage() function will also be transmitted through an open virtual output port. If a virtual input port is open and a user callback function is set, the callback function will be invoked when messages arrive via that port. If a callback function is not set, the user must poll the input queue to check whether messages have arrived. No notification is provided for the establishment of a client connection via a virtual port. The RtMidi::isPortOpen() function does not report the status of ports created with the RtMidi::openVirtualPort() function. + +\section compiling Compiling + +In order to compile RtMidi for a specific OS and API, it is necessary to supply the appropriate preprocessor definition and library within the compiler statement: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OS:MIDI API:Preprocessor Definition:Library or Framework:Example Compiler Statement:
LinuxALSA Sequencer__LINUX_ALSA__asound, pthreadg++ -Wall -D__LINUX_ALSA__ -o midiprobe midiprobe.cpp RtMidi.cpp -lasound -lpthread
Linux or MacJACK MIDI__UNIX_JACK__jackg++ -Wall -D__UNIX_JACK__ -o midiprobe midiprobe.cpp RtMidi.cpp -ljack
Macintosh OS XCoreMIDI__MACOSX_CORE__CoreMIDI, CoreAudio, CoreFoundationg++ -Wall -D__MACOSX_CORE__ -o midiprobe midiprobe.cpp RtMidi.cpp -framework CoreMIDI -framework CoreAudio -framework CoreFoundation
WindowsMultimedia Library__WINDOWS_MM__winmm.lib, multithreadedcompiler specific
+

+ +The example compiler statements above could be used to compile the midiprobe.cpp example file, assuming that midiprobe.cpp, RtMidi.h and RtMidi.cpp all exist in the same directory. + +\section debug Debugging + +If you are having problems getting RtMidi to run on your system, try passing the preprocessor definition __RTMIDI_DEBUG__ to the compiler (or define it in RtMidi.h). A variety of warning messages will be displayed that may help in determining the problem. Also try using the programs included in the tests directory. The program midiprobe displays the queried capabilities of all MIDI ports found. + +\section multi Using Simultaneous Multiple APIs + +Support for each MIDI API is encapsulated in specific MidiInApi or MidiOutApi subclasses, making it possible to compile and instantiate multiple API-specific subclasses on a given operating system. For example, one can compile both CoreMIDI and JACK support on the OS-X operating system by providing the appropriate preprocessor definitions for each. In a run-time situation, one might first attempt to determine whether any JACK ports are available. This can be done by specifying the api argument RtMidi::UNIX_JACK when attempting to create an instance of RtMidiIn or RtMidiOut. If no available ports are found, then an instance of RtMidi with the api argument RtMidi::MACOSX_CORE can be created. Alternately, if no api argument is specified, RtMidi will first look for JACK ports and if none are found, then CoreMIDI ports (in linux, the search order is JACK and then ALSA. In theory, it should also be possible to have separate instances of RtMidi open at the same time with different underlying API support, though this has not been tested. + +The static function RtMidi::getCompiledApi() is provided to determine the available compiled API support. The function RtMidi::getCurrentApi() indicates the API selected for a given RtMidi instance. + +\section apinotes API Notes + +RtMidi is designed to provide a common API across the various supported operating systems and audio libraries. Despite that, some issues should be mentioned with regard to each. + +\subsection linux Linux: + +RtMidi for Linux was developed using the Fedora distribution. Two different MIDI APIs are supported on Linux platforms: ALSA and JACK. A decision was made to not include support for the OSS API because the OSS API provides very limited functionality and because ALSA support is now incorporated in the Linux kernel. The ALSA sequencer and JACK APIs allows for virtual software input and output ports. + +\subsection macosx Macintosh OS X (CoreAudio): + +The Apple CoreMIDI API allows for the establishment of virtual input and output ports to which other software applications can connect. + +The RtMidi JACK support can be compiled on Macintosh OS-X systems, as well as in Linux. + +\subsection windowsds Windows (Multimedia Library): + +The \c configure script provides support for the MinGW compiler. + +The Windows Multimedia library MIDI calls used in RtMidi do not make use of streaming functionality. Incoming system exclusive messages read by RtMidiIn are limited to a length as defined by the preprocessor definition RT_SYSEX_BUFFER_SIZE (set in RtMidi.cpp). The default value is 1024. There is no such limit for outgoing sysex messages via RtMidiOut. + +RtMidi was originally developed with Visual C++ version 6.0 but has been tested with Virtual Studio 2010. + +\section acknowledge Development & Acknowledgements + +RtMidi is on github (https://github.com/thestk/rtmidi). Many thanks to the developers that are helping to maintain and improve RtMidi. + +In years past, the following people provided bug fixes and improvements: + +- Stephen Sinclair (Git repo, code and build system) +- amosonn +- Christopher Arndt +- Atsushi Eno (C API) +- Sebastien Alaiwan (JACK memory leaks, Windows kernel streaming) +- Jean-Baptiste Berruchon (Windows sysex code) +- Pedro Lopez-Cabanillas (ALSA sequencer API, client naming) +- Jason Champion (MSW project file for library build) +- Chris Chronopoulos +- JP Cimalando +- Eduardo Coutinho (Windows device names) +- Mattes D +- Michael Dahl +- Paul Dean (increment optimization) +- Francisco Demartino +- Luc Deschenaux (sysex issues) +- John Dey (OS-X timestamps) +- Christoph Eckert (ALSA sysex fixes) +- Thiago Goulart +- Ashley Hedges +- Sam Hocevar +- Rorey Jaffe +- jgvictores +- Martin Koegler (various fixes) +- Immanuel Litzroth (OS-X sysex fix) +- Bartek Lukawski +- Andi McClure +- Jon McCormack (Snow Leopard updates) +- Phildo +- Lane Spangler +- Axel Schmidt (client naming) +- Ryan Schmidt +- Saga Musix +- Bart Spaans +- Alexander Svetalkin (JACK MIDI) +- Ben Swift +- Casey Tucker (OS-X driver information, sysex sending) +- Bastiaan Verreijt (Windows sysex multi-buffer code) +- Dan Wilcox +- Yuri +- Serge Zaitsev +- Iohannes Zmölnig + +\section license License + + RtMidi: realtime MIDI i/o C++ classes
+ Copyright (c) 2003-2019 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ diff --git a/extern/rtmidi/doc/images/ccrma.gif b/extern/rtmidi/doc/images/ccrma.gif new file mode 100644 index 0000000000000000000000000000000000000000..8a7418ca48a6739930ec9487280c027ecb08db10 GIT binary patch literal 3527 zcmWlb`#;kQ1AxC5+w5W&Hs(GVR)%3jXtNk8z--VLJ-4_E80Q>+p@IU_V#7FCq3UMggqHMT1 z=jM)84HK*m!G&$!%nD3VcHO#Tid(`@Pj0UikotFx_-NjB{>P|nER$c(|*HFI&n|9r}yzPdq8J!OJ*wJ=T3+JZf0X2G>wak*m!xQe&R z-;CXjvc8U3lln-QK^#utOY`;eBuHV3si~j6E6>Vz)m6&F03O8`2yo*C*pEwXv6cb8 z<_zBe;F|x-pL}C|e0Z*|zL|zrbbEzbP)v>lTJ2M2M{VfuT2mHhsEAQ&nrnM%y0i#Q%zE^yaXLv)Qu-Hs()I>oifu7m!-tpPf#Z1N zCLAj+rQguR;>}#`38SH+&E0WKMveXXXpobvtBoBG0!{qAgId9Kl>B$9rrOI{f+nFd z9m4-<#6Rw#DpwL#^rx^$WY>_Sx+jrAGN2aD(g{4kW8>PO_`2uIe?EWwcNr3~hPVWOG=<;w%RGI^zCLKk2U;4rZ6AYu0gx%&deO4T$9J=-%nk3=IE)c z8TAp~z1H+2&LQgATuXziQm8vgSLVQ`A_7h?Yu{1Gk?EO2Th6*OY)GuS43hv2AWL58 z=JeKP>{S&VA^J=hV(I>X%vNx!|7Tu6{MK#&Sm- z>ysj53yab#RvnyqbZ}u!k;VsDSa#I?cIa$l{>#%{PhI45``rHEd?~HOFwR_>GO)>K zkeK36So(f-WG_CYBl6SclY{uWB?jbXS3AvM&~y z-sXvJlH@%T5c-OVB+b_M-F{I+sZSFhBMgiiN9F&n<-coCdh|lp|I4s~q3}~TM&W_b z_gpVLp?fx?5U=HhdTCOvFy^<^5YaGOY4MFd-N!<&7R|77tBX?YGyH0Jiu+H(^0>P#1~Ed`^(V!ic8QRU)=H<+GVy*PU{NH^?{ zaeFnioKFS|bZVgIGC;~@Gy;tbzJ2q_egk`{piK*$$f@Dwh^15W2pC`zh+!k;c_#s2 zjbBOkq->KTz?7zZ^-QhX6Vnq@+ttp_m2Y0#|xR^c*MX>GToe}*-H;kxQ_}seELy>6AGnt zO6&Lf)mC22u^g_)N0EVk^-Ik#?dPbioZr6sEL~lE81+P?o_dqe#WPK*S*_ZqZnFyw z2zny@b0$!4cj?%L9hN_o_uM(_-T5VO8{=>1$F3LCn1k7m zZf*KdxP&rjl@kW>-XAv__GBcMw)#jQx1ptI>SnmW3`Dcz<2y zoLm)-JcS%uh0AsF7tsvg*crfp27&S)wKAh&I)S@d2#8g76D33?hc90iK2b!R^@hT) z%HM8l=7p3%yW!_nkeLYkbPNL6q2;$X`uWeq>tyWusF{#q)GAfRp1R)y=h#{_Ay|R0 zJ1126F4bK*{C+sh)!IH()dsZ=HTcy|Hs&MOop(d4{WXDq?UEo-4@#wU`t60>NxYug z(V0KE-5uktf=~wP{agPIe2oi5$Edn;Sggf!^Y0`7obt|qkh!! zTWoY~&T)yRG*+6f+ee7Y9KA3oioxdcQ8osvB8d5tj?QKpC-N^V+6v}+D{_DEQ3hsr z(>`xOo{+l82(^FmL%l-(fr-CF!D#u7m(y5Gj~0{gX7=u%W+6bIQ7)}E-rm?&uBBc%2Cdn9Wg;%?~V3z^Lmc=Yy*}C+BBx3M=P7JkrA$#&O zv^JaXp!u0Eb~7AhFFt6&ks%b>LZe2n?UUT#8CM*s3M!qbM>$`EWzxJJFfn&*U7VO? z)uz%V`>)}>rz~+l&6hzbfK~CHWBnEFPQMxy``o4+xA6g%AkI+1v;G?4*;bMM>W$xU zS4_j8JZpYZphJ$B6gI)&M_}hLx(h5-?#!6eD`drM2Uq-O;b!-T%TN{^i_@W-U;9rF z1;}MoaOX};;k^);k?hA<6U_B;`uM|en!t^(2M=YCv0{&vxg@ z?8*bA375a9t`za|vIQ{Uqi1t;;a5)!vcrI@XAI&@~*Ezv-@T?zxp!C?u& z%_apf4AxzOmrGpOBBPNin!h%@KgVNins{RgBI)1)0BKO=DxPiu4Jssm9N_d9k z|LNpmi;5adhwQ829SaikFn1b!oGO8HAv<=ZQQ;!IVNzJ+Kt7C$CTPiz`s9td6tK!| z>_=6or&1oL{G~CzF3;>+V~i$B)_g^AH+>^1G?tp~PF4Xs=gMad6DiuNJznk(Sa7i8 zxK1+Uv9eQ;Yyfl2E4@r$@4#KK4rOVH6xCjJV@b+me@H!nblu zNdpyDv0;SX39C;s_;6<8m6k+lRNkhFvNCP#oZM)gA_-K%EHc=6(tNZfx(mDM9o8aE h0pH8p^!G#CEm3aJGSugj4PUP|e0$g+A{zjH{{@2vr#t`v literal 0 HcmV?d00001 diff --git a/extern/rtmidi/doc/images/mcgill.gif b/extern/rtmidi/doc/images/mcgill.gif new file mode 100644 index 0000000000000000000000000000000000000000..c571e3701b29cba905de133dda9ed646f14c974f GIT binary patch literal 4614 zcmeHJ_gB;R_s$}V5Z;**8BLf$DFz4|Bne>)BoSp)ZP+M|5ELg$NZ0|x6cjNmN576B zxT_`%4QgAY)?)EkK)_L|f?Dy@lGn$6fBXIq-{;)({BVD`_niAY&p9_DD%{UM^)t*L z_6hbcz!!9OgPXU&t=p!1_rZf7K;02g-wYaCmKvHtdpEdn5$JlC&i9xuURvtu1Gn_x zcK^~%J-Gh^7<*#+b<8w917=^CW@bTBI>_7svh$X*WFRLGL=8&SOC?pHtOnFJgZiUO|L!`r)NmBEp9CGJK<8#oZ;MZ|r8wBivmzaSj`_;f9*km1O zvJL{atASkzn3w@GufY6!fb;+;FF*+a4lzKGX-Yc;3mcFoOGVn?PyiCB$0)i|c z$~Q^!!P=cbdI(61Kw1%yRRei7IMiY~)C?3Y;BYrMd=hB7LG4L!q8Df{f(y5S?l!pn z0O%ippPqn`CtzY0{5}ibFPIkQ!Th}G{rfK>|F7ErPygovtj{Po4Au#A`?vdVCSX_q zi@@1L>a+$Wma<}lQP)0D2028xM)q_JRatp$d1~zGynC4Dn>mU))8!E964TMphZ!eeAlEM@5~{$MN$&I0}UK8zz$@ zevJ2&{-HmUUX~l*CH!f@jXG@c%N5!4;ztcemzMU-Lv81Jk$0-hI|=Il3~J3R+{16I zpySI3tQfgNOxsfqCHZDv&0mgd%kpgv=5gu{&4d|Yc_&$RX&{-{aw5xC+!p&T_N4cU zwOh7)E}gZGX|TKXaJrz=xhs5csZ8SY<5(Ltyqo6{0Jn6p3%^v39Ivsp!`&MNtXvHF z&*4`2>&cdcxLNJ9KVJHyKC>LXc8zRJIqhm;*I|)YS8BJ*s=wAb*gLVz0%y!?Qto^t zQjkb)Oe-@QwohQYbI+A^2()JMcy?LPqhUw4YOW-PEgrQF+aZ(~)g@YfB+Hkc7S71p zK(4peeBhVnhWkf0*vR~hYg2-03sZt>eS3@D5N$f#q0lC(Nw9s5ePnSaRW7v+aD)kL zm#ZakdXiSdOU!sAJGo5$h*L>%#rAEqC2%K%wv0ETyLgKn&F`Tlni;A1?MJGyo@6#f z>ugxzii_Od)stAa>qC3F(_nRfg>#czT4{2S_oyiTxl`t*Z51nk|8x^(o4{E2?A#E^ zc~^!&Qj7){U)qyS^`7T zlv&nn%^zKBPHmkyg>i9&qAh16Qog0XN{TcK+@nzxga><6T7!7ktks$@^r>CU z595zF?!{nOHvUu6WsFA<9M0u(!cpWySwaM74G_W5s~n34+4$+-ijP`}AY1lEW=p=X zl^Rwi+QgU}ajuGXU;kC;?!Jm&T7Q_#X|zbLOS0X@$-rete13S1SM}gO`L`FHPVX2$ zc22J}M>y*GF~KrgSuf&A6lA`8sCEcPH0pU+`&Q_#O&)fQm^EukXc~dOMkM?el1Qew z?AQ6e>LmYCvMcncjilV+f_VnRMmO9e>KGoH_Y0?gQknJnME0{a+UB;PTqc5w?Tdp^ z1DjzS#2Fs(p5{p9o>i`!dj{eMUub-r4aD@}HOa<0@59`4UReg@E7f~`U`D^+^wa|Z zbJ9jT_tu`PN=a$pHgr4<6GN|C{sh-M4dIjB_GLO{Em_#GI;e|mWSEOLtt}%LbdfC7 z%Wtz06+Jga4lPzy>z%Ja5vZ_4>pWF|sXfN6h+knG-jMv;%eKXx;Zapv`ZqrHYVb=? zWzTrtBl;U5{y!?-ZiQ9+t(T;W&~ro7J;Lzwdg~Nb>GVbA#fy5z z1FRDxPHx1?vNpr6GTzDv1Hh8LdW*@fQ}PC3Mq#CFK&+*~-3DrKr+# zptE+Uo|cEodd*Z~vnC@C{4-1>8X zVtU*q5WO5)CW!HV&q4G13;{prNru?@PObZ`h*sp8qAMrAg!4ceQ#+y%Cq-cV6Y`l6 zFZ<*B6KZ+?WKbKil|MP2f&Q6G_1n60L}N94Yj4va+06-SFAY6^CN`V`#ui&foX3m+DXtRo!T}cBlrEp?XNT#tod>)PXx{^4j-pqH)YY0;#_+cyV1zSie zLJ8dm{|p$6K`)>^-Z&BG>v7KHo)gNXtm=aaSPQS=%atEdD69v<>Yr?t(oKQm@lW%u zq}2Oi(;E;yR=HzO9809##8vP=!uNYVRtVFA!*GLFjWgP>YY-Ib24{wwKdoxm(1Tf- z$hhMZdLF2qz9SoiHbN~_vV|3j?cpt(7MR`+ z+40QsXiQycXJd(1B@auV6n&+8(@l8mWOTkWR3G@L@f^zYKCki&yXO(>+E?>~jtIk1 zzo%jUgY^GZP7%bPOK5W76yXVvSCCMydCZ?}>sE(RH!3IEiCKHZ)^ll1({hBRjx|t% z>#qp>09m@mjqjg>q}3wwfeo$$c$~el7n{Jo+E|X)N{TD}$UJ1;*q&vLXl3(xK-|=L zV$(g@s)FMj+SA12u!C;YVS187w&A z$uR`es6jUS6Q8rnt=WMu>glR%xwfa8!ljNm7ZaY>#&0m2Ds?%YJQN&tym4JaMV;H% z1kGZ6ny?FM?&EB)Bmwq9_UUg+?yIC=AWS@&`h|LZ$eI0uFH}+``3FWps&i=Zd)P2Ev?*IZ@ zZ1(99+`Cf>v)m`r5Zv%YqydKD1ScF5bdl#=jhf_ZoS>kn>O9b#vij1gH^BpO_jZ+Y zLWwt9294t^dglc8kWar|CAAQ-SD7q04%KF5J=Toaoo=)x*+Y()$|nNL>ryIPmcmSW z$-+?I!9Pv>%H%YEbiSDmot;~&LO)m96tB^+RuaCg38uBq=`u?l_x*0?`%PTdAg0%^ zw_Y=Xs>YDFa~)7#LjF9@z1F{V?;v?4CDOfHMbD*mA)=qgq#TeztOIrI8h7jkpU%)t z3zdXz=VKGg6X^F-J@YBAls45<^70&kx&zC#RvLxX?szL#QrkHK{L;+k^wMbCg+yDZDVGkseyBDpP)Dwh^#RSpIRPTi+tP;AMwQ7$hh}aW&|mWBlAPR zk-I)Y)l3fzNwRyVW8nl^YDh6_w<>nV9OgksN70jlqZI6wl|T64!?j5Bd`7kyhRWO4@}e-A7vvx#E&g1LHefUv?pL!hs=-X+ zi_|71x>vZ8G>`u9(W_R!4d-d+*>z}os`Rzbfowh%z6fD?P@K+@#e;k6VH>X?ExUPZ zbnIXq!UYP-)aTJOyCE^w3aFqheW7isn624RLLXZ`Az!R#IQ#5C*9vC^)NCHf&nfWP z`kfJS>d^XpV*|8%hOz4vG8yM=VCQ;?V<$0;J1AC%wD zP@LCeLXD&yvNAFs?j?eAINOkUa4Y?OOFni}DfCzIs&AVW!5_F8S~N*bNNt}mw(oJ2?_p#>RkkWM+JKCGhdKc#ErV$+ z`9Ao`VCPxZ>L{qCm+}5(@MioAS|j~=B9uH!x!uBcVb^>M)}iaYB0Sr1!x4}* zZ1d5FtiMYjo|bfcgd#y`yvJy_{qe+8w0LD9YLas{BTy;A1<#_Jim=N+tlb5tQm2`F znQe$3+~#%$?=rN(5OCwk{%k6yTnp6<21M1iTYH!#`r?CR$`|&SSGa=DI8u^|zK_|_ zDZRa~(?I)PObot`%A=Uv2nqgb(L z!X9bBRcYwlUnqq-v}OwZ=MkGQgM&tUnr6VQ6hQ>3yN!HDfgY~#!$(18^i*2eyY#Ko z`}Y|zx*;rXJydu{9(|Q@_MuWPbL}g|?*4SJK!0=SSnStjfDOn>}#^RUB zJKmg<5bJQwI?6eZh#(LDa0TXJCay7>)0D#Dq~eCWAtxv@Sda9F(W$lg;-adkB$|sW)vuU;Zoi7{f%TQ7{taJUpvC0CdgJox7Jrix^oK1pOpy+S$b+xa zqv$z^V4tYNFkHRP8p|TP&u{ +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) diff --git a/extern/rtmidi/msw/readme b/extern/rtmidi/msw/readme new file mode 100644 index 00000000..e692d6dc --- /dev/null +++ b/extern/rtmidi/msw/readme @@ -0,0 +1 @@ +This directory contains a Visual Studio 2008 project, contributed by Jason Champion, to build rtmidi as a library. The library builds to the \lib directory. \ No newline at end of file diff --git a/extern/rtmidi/msw/rtmidilib.sln b/extern/rtmidi/msw/rtmidilib.sln new file mode 100755 index 00000000..7302ae57 --- /dev/null +++ b/extern/rtmidi/msw/rtmidilib.sln @@ -0,0 +1,20 @@ +ï»ż +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rtmidilib", "rtmidilib.vcproj", "{EBFE5EB3-182A-47A6-922B-52ECF777F6A3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EBFE5EB3-182A-47A6-922B-52ECF777F6A3}.Debug|Win32.ActiveCfg = Debug|Win32 + {EBFE5EB3-182A-47A6-922B-52ECF777F6A3}.Debug|Win32.Build.0 = Debug|Win32 + {EBFE5EB3-182A-47A6-922B-52ECF777F6A3}.Release|Win32.ActiveCfg = Release|Win32 + {EBFE5EB3-182A-47A6-922B-52ECF777F6A3}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extern/rtmidi/msw/rtmidilib.vcproj b/extern/rtmidi/msw/rtmidilib.vcproj new file mode 100755 index 00000000..20a458f0 --- /dev/null +++ b/extern/rtmidi/msw/rtmidilib.vcproj @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extern/rtmidi/rtmidi-config.in b/extern/rtmidi/rtmidi-config.in new file mode 100644 index 00000000..34d2a25d --- /dev/null +++ b/extern/rtmidi/rtmidi-config.in @@ -0,0 +1,19 @@ +#! /bin/sh +if (test "x$#" != "x1") ; then + echo "Usage: $0 [--libs | --cxxflags | --cppflags]" + exit; +fi + +LIBRARY="@LIBS@" +CXXFLAGS="@CXXFLAGS@" +CPPFLAGS="@CPPFLAGS@" + +if (test "x$1" = "x--libs") ; then + echo "$LIBRARY -lrtmidi" +elif (test "x$1" = "x--cxxflags") ; then + echo "$CXXFLAGS" +elif (test "x$1" = "x--cppflags") ; then + echo "$CPPFLAGS" +else + echo "Unknown option: $1" +fi diff --git a/extern/rtmidi/rtmidi.pc.in b/extern/rtmidi/rtmidi.pc.in new file mode 100644 index 00000000..acb6888c --- /dev/null +++ b/extern/rtmidi/rtmidi.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include/rtmidi + +Name: librtmidi +Description: RtMidi - a set of C++ classes that provide a common API for realtime MIDI input/output +Version: @PACKAGE_VERSION@ +Requires.private: @req@ +Libs: -L${libdir} -lrtmidi +Libs.private: -lpthread @req_libs@ +Cflags: -pthread -I${includedir} @api@ diff --git a/extern/rtmidi/rtmidi_c.cpp b/extern/rtmidi/rtmidi_c.cpp new file mode 100644 index 00000000..19f586e3 --- /dev/null +++ b/extern/rtmidi/rtmidi_c.cpp @@ -0,0 +1,372 @@ +#include +#include +#include "rtmidi_c.h" +#include "RtMidi.h" + +/* Compile-time assertions that will break if the enums are changed in + * the future without synchronizing them properly. If you get (g++) + * "error: ‘StaticAssert::StaticAssert() [with bool b = false]’ is + * private within this context", it means enums are not aligned. */ +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +#define ENUM_EQUAL(x,y) StaticAssert<(int)x==(int)y>() +class StaticAssertions { StaticAssertions() { + ENUM_EQUAL( RTMIDI_API_UNSPECIFIED, RtMidi::UNSPECIFIED ); + ENUM_EQUAL( RTMIDI_API_MACOSX_CORE, RtMidi::MACOSX_CORE ); + ENUM_EQUAL( RTMIDI_API_LINUX_ALSA, RtMidi::LINUX_ALSA ); + ENUM_EQUAL( RTMIDI_API_UNIX_JACK, RtMidi::UNIX_JACK ); + ENUM_EQUAL( RTMIDI_API_WINDOWS_MM, RtMidi::WINDOWS_MM ); + ENUM_EQUAL( RTMIDI_API_RTMIDI_DUMMY, RtMidi::RTMIDI_DUMMY ); + + ENUM_EQUAL( RTMIDI_ERROR_WARNING, RtMidiError::WARNING ); + ENUM_EQUAL( RTMIDI_ERROR_DEBUG_WARNING, RtMidiError::DEBUG_WARNING ); + ENUM_EQUAL( RTMIDI_ERROR_UNSPECIFIED, RtMidiError::UNSPECIFIED ); + ENUM_EQUAL( RTMIDI_ERROR_NO_DEVICES_FOUND, RtMidiError::NO_DEVICES_FOUND ); + ENUM_EQUAL( RTMIDI_ERROR_INVALID_DEVICE, RtMidiError::INVALID_DEVICE ); + ENUM_EQUAL( RTMIDI_ERROR_MEMORY_ERROR, RtMidiError::MEMORY_ERROR ); + ENUM_EQUAL( RTMIDI_ERROR_INVALID_PARAMETER, RtMidiError::INVALID_PARAMETER ); + ENUM_EQUAL( RTMIDI_ERROR_INVALID_USE, RtMidiError::INVALID_USE ); + ENUM_EQUAL( RTMIDI_ERROR_DRIVER_ERROR, RtMidiError::DRIVER_ERROR ); + ENUM_EQUAL( RTMIDI_ERROR_SYSTEM_ERROR, RtMidiError::SYSTEM_ERROR ); + ENUM_EQUAL( RTMIDI_ERROR_THREAD_ERROR, RtMidiError::THREAD_ERROR ); +}}; + +class CallbackProxyUserData +{ + public: + CallbackProxyUserData (RtMidiCCallback cCallback, void *userData) + : c_callback (cCallback), user_data (userData) + { + } + RtMidiCCallback c_callback; + void *user_data; +}; + +extern "C" const enum RtMidiApi rtmidi_compiled_apis[]; // casting from RtMidi::Api[] +extern "C" const unsigned int rtmidi_num_compiled_apis; + +/* RtMidi API */ +int rtmidi_get_compiled_api (enum RtMidiApi *apis, unsigned int apis_size) +{ + unsigned num = rtmidi_num_compiled_apis; + if (apis) { + num = (num < apis_size) ? num : apis_size; + memcpy(apis, rtmidi_compiled_apis, num * sizeof(enum RtMidiApi)); + } + return (int)num; +} + +extern "C" const char* rtmidi_api_names[][2]; +const char *rtmidi_api_name(enum RtMidiApi api) { + if (api < 0 || api >= RTMIDI_API_NUM) + return NULL; + return rtmidi_api_names[api][0]; +} + +const char *rtmidi_api_display_name(enum RtMidiApi api) +{ + if (api < 0 || api >= RTMIDI_API_NUM) + return "Unknown"; + return rtmidi_api_names[api][1]; +} + +enum RtMidiApi rtmidi_compiled_api_by_name(const char *name) { + RtMidi::Api api = RtMidi::UNSPECIFIED; + if (name) { + api = RtMidi::getCompiledApiByName(name); + } + return (enum RtMidiApi)api; +} + +void rtmidi_error (MidiApi *api, enum RtMidiErrorType type, const char* errorString) +{ + std::string msg = errorString; + api->error ((RtMidiError::Type) type, msg); +} + +void rtmidi_open_port (RtMidiPtr device, unsigned int portNumber, const char *portName) +{ + std::string name = portName; + try { + ((RtMidi*) device->ptr)->openPort (portNumber, name); + + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + } +} + +void rtmidi_open_virtual_port (RtMidiPtr device, const char *portName) +{ + std::string name = portName; + try { + ((RtMidi*) device->ptr)->openVirtualPort (name); + + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + } + +} + +void rtmidi_close_port (RtMidiPtr device) +{ + try { + ((RtMidi*) device->ptr)->closePort (); + + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + } +} + +unsigned int rtmidi_get_port_count (RtMidiPtr device) +{ + try { + return ((RtMidi*) device->ptr)->getPortCount (); + + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + return -1; + } +} + +int rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber, char * bufOut, int * bufLen) +{ + if (bufOut == nullptr && bufLen == nullptr) { + return -1; + } + + std::string name; + try { + name = ((RtMidi*) device->ptr)->getPortName (portNumber); + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + return -1; + } + + if (bufOut == nullptr) { + *bufLen = static_cast(name.size()) + 1; + return 0; + } + + return snprintf(bufOut, static_cast(*bufLen), "%s", name.c_str()); +} + +/* RtMidiIn API */ +RtMidiInPtr rtmidi_in_create_default () +{ + RtMidiWrapper* wrp = new RtMidiWrapper; + + try { + RtMidiIn* rIn = new RtMidiIn (); + + wrp->ptr = (void*) rIn; + wrp->data = 0; + wrp->ok = true; + wrp->msg = ""; + + } catch (const RtMidiError & err) { + wrp->ptr = 0; + wrp->data = 0; + wrp->ok = false; + wrp->msg = err.what (); + } + + return wrp; +} + +RtMidiInPtr rtmidi_in_create (enum RtMidiApi api, const char *clientName, unsigned int queueSizeLimit) +{ + std::string name = clientName; + RtMidiWrapper* wrp = new RtMidiWrapper; + + try { + RtMidiIn* rIn = new RtMidiIn ((RtMidi::Api) api, name, queueSizeLimit); + + wrp->ptr = (void*) rIn; + wrp->data = 0; + wrp->ok = true; + wrp->msg = ""; + + } catch (const RtMidiError & err) { + wrp->ptr = 0; + wrp->data = 0; + wrp->ok = false; + wrp->msg = err.what (); + } + + return wrp; +} + +void rtmidi_in_free (RtMidiInPtr device) +{ + if (device->data) + delete (CallbackProxyUserData*) device->data; + delete (RtMidiIn*) device->ptr; + delete device; +} + +enum RtMidiApi rtmidi_in_get_current_api (RtMidiPtr device) +{ + try { + return (RtMidiApi) ((RtMidiIn*) device->ptr)->getCurrentApi (); + + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + + return RTMIDI_API_UNSPECIFIED; + } +} + +static +void callback_proxy (double timeStamp, std::vector *message, void *userData) +{ + CallbackProxyUserData* data = reinterpret_cast (userData); + data->c_callback (timeStamp, message->data (), message->size (), data->user_data); +} + +void rtmidi_in_set_callback (RtMidiInPtr device, RtMidiCCallback callback, void *userData) +{ + device->data = (void*) new CallbackProxyUserData (callback, userData); + try { + ((RtMidiIn*) device->ptr)->setCallback (callback_proxy, device->data); + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + delete (CallbackProxyUserData*) device->data; + device->data = 0; + } +} + +void rtmidi_in_cancel_callback (RtMidiInPtr device) +{ + try { + ((RtMidiIn*) device->ptr)->cancelCallback (); + delete (CallbackProxyUserData*) device->data; + device->data = 0; + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + } +} + +void rtmidi_in_ignore_types (RtMidiInPtr device, bool midiSysex, bool midiTime, bool midiSense) +{ + ((RtMidiIn*) device->ptr)->ignoreTypes (midiSysex, midiTime, midiSense); +} + +double rtmidi_in_get_message (RtMidiInPtr device, + unsigned char *message, + size_t *size) +{ + try { + // FIXME: use allocator to achieve efficient buffering + std::vector v; + double ret = ((RtMidiIn*) device->ptr)->getMessage (&v); + + if (v.size () > 0 && v.size() <= *size) { + memcpy (message, v.data (), (int) v.size ()); + } + + *size = v.size(); + return ret; + } + catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + return -1; + } + catch (...) { + device->ok = false; + device->msg = "Unknown error"; + return -1; + } +} + +/* RtMidiOut API */ +RtMidiOutPtr rtmidi_out_create_default () +{ + RtMidiWrapper* wrp = new RtMidiWrapper; + + try { + RtMidiOut* rOut = new RtMidiOut (); + + wrp->ptr = (void*) rOut; + wrp->data = 0; + wrp->ok = true; + wrp->msg = ""; + + } catch (const RtMidiError & err) { + wrp->ptr = 0; + wrp->data = 0; + wrp->ok = false; + wrp->msg = err.what (); + } + + return wrp; +} + +RtMidiOutPtr rtmidi_out_create (enum RtMidiApi api, const char *clientName) +{ + RtMidiWrapper* wrp = new RtMidiWrapper; + std::string name = clientName; + + try { + RtMidiOut* rOut = new RtMidiOut ((RtMidi::Api) api, name); + + wrp->ptr = (void*) rOut; + wrp->data = 0; + wrp->ok = true; + wrp->msg = ""; + + } catch (const RtMidiError & err) { + wrp->ptr = 0; + wrp->data = 0; + wrp->ok = false; + wrp->msg = err.what (); + } + + + return wrp; +} + +void rtmidi_out_free (RtMidiOutPtr device) +{ + delete (RtMidiOut*) device->ptr; + delete device; +} + +enum RtMidiApi rtmidi_out_get_current_api (RtMidiPtr device) +{ + try { + return (RtMidiApi) ((RtMidiOut*) device->ptr)->getCurrentApi (); + + } catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + + return RTMIDI_API_UNSPECIFIED; + } +} + +int rtmidi_out_send_message (RtMidiOutPtr device, const unsigned char *message, int length) +{ + try { + ((RtMidiOut*) device->ptr)->sendMessage (message, length); + return 0; + } + catch (const RtMidiError & err) { + device->ok = false; + device->msg = err.what (); + return -1; + } + catch (...) { + device->ok = false; + device->msg = "Unknown error"; + return -1; + } +} diff --git a/extern/rtmidi/rtmidi_c.h b/extern/rtmidi/rtmidi_c.h new file mode 100644 index 00000000..efbf977e --- /dev/null +++ b/extern/rtmidi/rtmidi_c.h @@ -0,0 +1,251 @@ +/************************************************************************/ +/*! \defgroup C-interface + @{ + + \brief C interface to realtime MIDI input/output C++ classes. + + RtMidi offers a C-style interface, principally for use in binding + RtMidi to other programming languages. All structs, enums, and + functions listed here have direct analogs (and simply call to) + items in the C++ RtMidi class and its supporting classes and + types +*/ +/************************************************************************/ + +/*! + \file rtmidi_c.h + */ + +#include +#include +#ifndef RTMIDI_C_H +#define RTMIDI_C_H + +#if defined(RTMIDI_EXPORT) +#if defined _WIN32 || defined __CYGWIN__ +#define RTMIDIAPI __declspec(dllexport) +#else +#define RTMIDIAPI __attribute__((visibility("default"))) +#endif +#else +#define RTMIDIAPI //__declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +//! \brief Wraps an RtMidi object for C function return statuses. +struct RtMidiWrapper { + //! The wrapped RtMidi object. + void* ptr; + void* data; + + //! True when the last function call was OK. + bool ok; + + //! If an error occured (ok != true), set to an error message. + const char* msg; +}; + +//! \brief Typedef for a generic RtMidi pointer. +typedef struct RtMidiWrapper* RtMidiPtr; + +//! \brief Typedef for a generic RtMidiIn pointer. +typedef struct RtMidiWrapper* RtMidiInPtr; + +//! \brief Typedef for a generic RtMidiOut pointer. +typedef struct RtMidiWrapper* RtMidiOutPtr; + +//! \brief MIDI API specifier arguments. See \ref RtMidi::Api. +enum RtMidiApi { + RTMIDI_API_UNSPECIFIED, /*!< Search for a working compiled API. */ + RTMIDI_API_MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */ + RTMIDI_API_LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + RTMIDI_API_UNIX_JACK, /*!< The Jack Low-Latency MIDI Server API. */ + RTMIDI_API_WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ + RTMIDI_API_RTMIDI_DUMMY, /*!< A compilable but non-functional API. */ + RTMIDI_API_NUM /*!< Number of values in this enum. */ +}; + +//! \brief Defined RtMidiError types. See \ref RtMidiError::Type. +enum RtMidiErrorType { + RTMIDI_ERROR_WARNING, /*!< A non-critical error. */ + RTMIDI_ERROR_DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + RTMIDI_ERROR_UNSPECIFIED, /*!< The default, unspecified error type. */ + RTMIDI_ERROR_NO_DEVICES_FOUND, /*!< No devices found on system. */ + RTMIDI_ERROR_INVALID_DEVICE, /*!< An invalid device ID was specified. */ + RTMIDI_ERROR_MEMORY_ERROR, /*!< An error occured during memory allocation. */ + RTMIDI_ERROR_INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + RTMIDI_ERROR_INVALID_USE, /*!< The function was called incorrectly. */ + RTMIDI_ERROR_DRIVER_ERROR, /*!< A system driver error occured. */ + RTMIDI_ERROR_SYSTEM_ERROR, /*!< A system error occured. */ + RTMIDI_ERROR_THREAD_ERROR /*!< A thread error occured. */ +}; + +/*! \brief The type of a RtMidi callback function. + * + * \param timeStamp The time at which the message has been received. + * \param message The midi message. + * \param userData Additional user data for the callback. + * + * See \ref RtMidiIn::RtMidiCallback. + */ +typedef void(* RtMidiCCallback) (double timeStamp, const unsigned char* message, + size_t messageSize, void *userData); + + +/* RtMidi API */ + +/*! \brief Determine the available compiled MIDI APIs. + * + * If the given `apis` parameter is null, returns the number of available APIs. + * Otherwise, fill the given apis array with the RtMidi::Api values. + * + * \param apis An array or a null value. + * \param apis_size Number of elements pointed to by apis + * \return number of items needed for apis array if apis==NULL, or + * number of items written to apis array otherwise. A negative + * return value indicates an error. + * + * See \ref RtMidi::getCompiledApi(). +*/ +RTMIDIAPI int rtmidi_get_compiled_api (enum RtMidiApi *apis, unsigned int apis_size); + +//! \brief Return the name of a specified compiled MIDI API. +//! See \ref RtMidi::getApiName(). +RTMIDIAPI const char *rtmidi_api_name(enum RtMidiApi api); + +//! \brief Return the display name of a specified compiled MIDI API. +//! See \ref RtMidi::getApiDisplayName(). +RTMIDIAPI const char *rtmidi_api_display_name(enum RtMidiApi api); + +//! \brief Return the compiled MIDI API having the given name. +//! See \ref RtMidi::getCompiledApiByName(). +RTMIDIAPI enum RtMidiApi rtmidi_compiled_api_by_name(const char *name); + +//! \internal Report an error. +RTMIDIAPI void rtmidi_error (enum RtMidiErrorType type, const char* errorString); + +/*! \brief Open a MIDI port. + * + * \param port Must be greater than 0 + * \param portName Name for the application port. + * + * See RtMidi::openPort(). + */ +RTMIDIAPI void rtmidi_open_port (RtMidiPtr device, unsigned int portNumber, const char *portName); + +/*! \brief Creates a virtual MIDI port to which other software applications can + * connect. + * + * \param portName Name for the application port. + * + * See RtMidi::openVirtualPort(). + */ +RTMIDIAPI void rtmidi_open_virtual_port (RtMidiPtr device, const char *portName); + +/*! \brief Close a MIDI connection. + * See RtMidi::closePort(). + */ +RTMIDIAPI void rtmidi_close_port (RtMidiPtr device); + +/*! \brief Return the number of available MIDI ports. + * See RtMidi::getPortCount(). + */ +RTMIDIAPI unsigned int rtmidi_get_port_count (RtMidiPtr device); + +/*! \brief Access a string identifier for the specified MIDI input port number. + * + * To prevent memory leaks a char buffer must be passed to this function. + * NULL can be passed as bufOut parameter, and that will write the required buffer length in the bufLen. + * + * See RtMidi::getPortName(). + */ +RTMIDIAPI int rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber, char * bufOut, int * bufLen); + +/* RtMidiIn API */ + +//! \brief Create a default RtMidiInPtr value, with no initialization. +RTMIDIAPI RtMidiInPtr rtmidi_in_create_default (void); + +/*! \brief Create a RtMidiInPtr value, with given api, clientName and queueSizeLimit. + * + * \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. + * + * See RtMidiIn::RtMidiIn(). + */ +RTMIDIAPI RtMidiInPtr rtmidi_in_create (enum RtMidiApi api, const char *clientName, unsigned int queueSizeLimit); + +//! \brief Free the given RtMidiInPtr. +RTMIDIAPI void rtmidi_in_free (RtMidiInPtr device); + +//! \brief Returns the MIDI API specifier for the given instance of RtMidiIn. +//! See \ref RtMidiIn::getCurrentApi(). +RTMIDIAPI enum RtMidiApi rtmidi_in_get_current_api (RtMidiPtr device); + +//! \brief Set a callback function to be invoked for incoming MIDI messages. +//! See \ref RtMidiIn::setCallback(). +RTMIDIAPI void rtmidi_in_set_callback (RtMidiInPtr device, RtMidiCCallback callback, void *userData); + +//! \brief Cancel use of the current callback function (if one exists). +//! See \ref RtMidiIn::cancelCallback(). +RTMIDIAPI void rtmidi_in_cancel_callback (RtMidiInPtr device); + +//! \brief Specify whether certain MIDI message types should be queued or ignored during input. +//! See \ref RtMidiIn::ignoreTypes(). +RTMIDIAPI void rtmidi_in_ignore_types (RtMidiInPtr device, bool midiSysex, bool midiTime, bool midiSense); + +/*! Fill the user-provided array with the data bytes for the next available + * MIDI message in the input queue and return the event delta-time in seconds. + * + * \param message Must point to a char* that is already allocated. + * SYSEX messages maximum size being 1024, a statically + * allocated array could + * be sufficient. + * \param size Is used to return the size of the message obtained. + * Must be set to the size of \ref message when calling. + * + * See RtMidiIn::getMessage(). + */ +RTMIDIAPI double rtmidi_in_get_message (RtMidiInPtr device, unsigned char *message, size_t *size); + +/* RtMidiOut API */ + +//! \brief Create a default RtMidiInPtr value, with no initialization. +RTMIDIAPI RtMidiOutPtr rtmidi_out_create_default (void); + +/*! \brief Create a RtMidiOutPtr value, with given and clientName. + * + * \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. + * + * See RtMidiOut::RtMidiOut(). + */ +RTMIDIAPI RtMidiOutPtr rtmidi_out_create (enum RtMidiApi api, const char *clientName); + +//! \brief Free the given RtMidiOutPtr. +RTMIDIAPI void rtmidi_out_free (RtMidiOutPtr device); + +//! \brief Returns the MIDI API specifier for the given instance of RtMidiOut. +//! See \ref RtMidiOut::getCurrentApi(). +RTMIDIAPI enum RtMidiApi rtmidi_out_get_current_api (RtMidiPtr device); + +//! \brief Immediately send a single message out an open MIDI output port. +//! See \ref RtMidiOut::sendMessage(). +RTMIDIAPI int rtmidi_out_send_message (RtMidiOutPtr device, const unsigned char *message, int length); + + +#ifdef __cplusplus +} +#endif +#endif + +/*! }@ */ diff --git a/extern/rtmidi/tests/Debug/.placeholder b/extern/rtmidi/tests/Debug/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/extern/rtmidi/tests/Makefile.am b/extern/rtmidi/tests/Makefile.am new file mode 100644 index 00000000..9283cde1 --- /dev/null +++ b/extern/rtmidi/tests/Makefile.am @@ -0,0 +1,38 @@ + +noinst_PROGRAMS = midiprobe midiout qmidiin cmidiin sysextest midiclock_in midiclock_out \ + apinames testcapi + +AM_CXXFLAGS = -Wall -I$(top_srcdir) +AM_CFLAGS = -Wall -I$(top_srcdir) + +midiprobe_SOURCES = midiprobe.cpp +midiprobe_LDADD = $(top_builddir)/librtmidi.la + +midiout_SOURCES = midiout.cpp +midiout_LDADD = $(top_builddir)/librtmidi.la + +qmidiin_SOURCES = qmidiin.cpp +qmidiin_LDADD = $(top_builddir)/librtmidi.la + +cmidiin_SOURCES = cmidiin.cpp +cmidiin_LDADD = $(top_builddir)/librtmidi.la + +sysextest_SOURCES = sysextest.cpp +sysextest_LDADD = $(top_builddir)/librtmidi.la + +midiclock_in_SOURCES = midiclock.cpp +midiclock_in_LDADD = $(top_builddir)/librtmidi.la + +midiclock_out_SOURCES = midiclock.cpp +midiclock_out_LDADD = $(top_builddir)/librtmidi.la + +apinames_SOURCES = apinames.cpp +apinames_LDADD = $(top_builddir)/librtmidi.la + +testcapi_SOURCES = testcapi.c +testcapi_LDADD = $(top_builddir)/librtmidi.la + +EXTRA_DIST = cmidiin.dsp midiout.dsp midiprobe.dsp qmidiin.dsp \ + sysextest.dsp RtMidi.dsw + +TESTS = apinames diff --git a/extern/rtmidi/tests/Release/.placeholder b/extern/rtmidi/tests/Release/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/extern/rtmidi/tests/RtMidi.dsw b/extern/rtmidi/tests/RtMidi.dsw new file mode 100644 index 00000000..4ab1cdce --- /dev/null +++ b/extern/rtmidi/tests/RtMidi.dsw @@ -0,0 +1,77 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "cmidiin"=".\cmidiin.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "midiout"=".\midiout.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "midiprobe"=".\midiprobe.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "qmidiin"=".\qmidiin.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "sysextest"=".\sysextest.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/extern/rtmidi/tests/apinames.cpp b/extern/rtmidi/tests/apinames.cpp new file mode 100644 index 00000000..d3c9791b --- /dev/null +++ b/extern/rtmidi/tests/apinames.cpp @@ -0,0 +1,159 @@ +/******************************************/ +/* + apinames.cpp + by Jean Pierre Cimalando, 2018. + + This program tests parts of RtMidi related + to API names, the conversion from name to API + and vice-versa. +*/ +/******************************************/ + +#include "RtMidi.h" +#include +#include +#include + +int test_cpp() { + std::vector apis; + RtMidi::getCompiledApi( apis ); + + // ensure the known APIs return valid names + std::cout << "API names by identifier (C++):\n"; + for ( size_t i = 0; i < apis.size() ; ++i ) { + const std::string name = RtMidi::getApiName(apis[i]); + if (name.empty()) { + std::cout << "Invalid name for API " << (int)apis[i] << "\n"; + exit(1); + } + const std::string displayName = RtMidi::getApiDisplayName(apis[i]); + if (displayName.empty()) { + std::cout << "Invalid display name for API " << (int)apis[i] << "\n"; + exit(1); + } + std::cout << "* " << (int)apis[i] << " '" << name << "': '" << displayName << "'\n"; + } + + // ensure unknown APIs return the empty string + { + const std::string name = RtMidi::getApiName((RtMidi::Api)-1); + if (!name.empty()) { + std::cout << "Bad string for invalid API '" << name << "'\n"; + exit(1); + } + const std::string displayName = RtMidi::getApiDisplayName((RtMidi::Api)-1); + if (displayName!="Unknown") { + std::cout << "Bad display string for invalid API '" << displayName << "'\n"; + exit(1); + } + } + + // try getting API identifier by name + std::cout << "API identifiers by name (C++):\n"; + for ( size_t i = 0; i < apis.size() ; ++i ) { + std::string name = RtMidi::getApiName(apis[i]); + if ( RtMidi::getCompiledApiByName(name) != apis[i] ) { + std::cout << "Bad identifier for API '" << name << "'\n"; + exit( 1 ); + } + std::cout << "* '" << name << "': " << (int)apis[i] << "\n"; + + for ( size_t j = 0; j < name.size(); ++j ) + name[j] = (j & 1) ? toupper(name[j]) : tolower(name[j]); + RtMidi::Api api = RtMidi::getCompiledApiByName(name); + if ( api != RtMidi::UNSPECIFIED ) { + std::cout << "Identifier " << (int)api << " for invalid API '" << name << "'\n"; + exit( 1 ); + } + } + + // try getting an API identifier by unknown name + { + RtMidi::Api api; + api = RtMidi::getCompiledApiByName(""); + if ( api != RtMidi::UNSPECIFIED ) { + std::cout << "Bad identifier for unknown API name\n"; + exit( 1 ); + } + } + + return 0; +} + +#include "rtmidi_c.h" + +int test_c() { + unsigned api_count = rtmidi_get_compiled_api(NULL, 0); + std::vector apis(api_count); + rtmidi_get_compiled_api(apis.data(), api_count); + + // ensure the known APIs return valid names + std::cout << "API names by identifier (C):\n"; + for ( size_t i = 0; i < api_count; ++i) { + const std::string name = rtmidi_api_name(apis[i]); + if (name.empty()) { + std::cout << "Invalid name for API " << (int)apis[i] << "\n"; + exit(1); + } + const std::string displayName = rtmidi_api_display_name(apis[i]); + if (displayName.empty()) { + std::cout << "Invalid display name for API " << (int)apis[i] << "\n"; + exit(1); + } + std::cout << "* " << (int)apis[i] << " '" << name << "': '" << displayName << "'\n"; + } + + // ensure unknown APIs return the empty string + { + const char *s = rtmidi_api_name((RtMidiApi)-1); + const std::string name(s?s:""); + if (!name.empty()) { + std::cout << "Bad string for invalid API '" << name << "'\n"; + exit(1); + } + s = rtmidi_api_display_name((RtMidiApi)-1); + const std::string displayName(s?s:""); + if (displayName!="Unknown") { + std::cout << "Bad display string for invalid API '" << displayName << "'\n"; + exit(1); + } + } + + // try getting API identifier by name + std::cout << "API identifiers by name (C):\n"; + for ( size_t i = 0; i < api_count ; ++i ) { + const char *s = rtmidi_api_name(apis[i]); + std::string name(s?s:""); + if ( rtmidi_compiled_api_by_name(name.c_str()) != apis[i] ) { + std::cout << "Bad identifier for API '" << name << "'\n"; + exit( 1 ); + } + std::cout << "* '" << name << "': " << (int)apis[i] << "\n"; + + for ( size_t j = 0; j < name.size(); ++j ) + name[j] = (j & 1) ? toupper(name[j]) : tolower(name[j]); + RtMidiApi api = rtmidi_compiled_api_by_name(name.c_str()); + if ( api != RTMIDI_API_UNSPECIFIED ) { + std::cout << "Identifier " << (int)api << " for invalid API '" << name << "'\n"; + exit( 1 ); + } + } + + // try getting an API identifier by unknown name + { + RtMidiApi api; + api = rtmidi_compiled_api_by_name(""); + if ( api != RTMIDI_API_UNSPECIFIED ) { + std::cout << "Bad identifier for unknown API name\n"; + exit( 1 ); + } + } + + return 0; +} + +int main() +{ + test_cpp(); + test_c(); +} diff --git a/extern/rtmidi/tests/cmidiin.cpp b/extern/rtmidi/tests/cmidiin.cpp new file mode 100644 index 00000000..71900faa --- /dev/null +++ b/extern/rtmidi/tests/cmidiin.cpp @@ -0,0 +1,111 @@ +//*****************************************// +// cmidiin.cpp +// by Gary Scavone, 2003-2004. +// +// Simple program to test MIDI input and +// use of a user callback function. +// +//*****************************************// + +#include +#include +#include "RtMidi.h" + +void usage( void ) { + // Error function in case of incorrect command-line + // argument specifications. + std::cout << "\nuseage: cmidiin \n"; + std::cout << " where port = the device to use (first / default = 0).\n\n"; + exit( 0 ); +} + +void mycallback( double deltatime, std::vector< unsigned char > *message, void */*userData*/ ) +{ + unsigned int nBytes = message->size(); + for ( unsigned int i=0; i 0 ) + std::cout << "stamp = " << deltatime << std::endl; +} + +// This function should be embedded in a try/catch block in case of +// an exception. It offers the user a choice of MIDI ports to open. +// It returns false if there are no ports available. +bool chooseMidiPort( RtMidiIn *rtmidi ); + +int main( int argc, char ** /*argv[]*/ ) +{ + RtMidiIn *midiin = 0; + + // Minimal command-line check. + if ( argc > 2 ) usage(); + + try { + + // RtMidiIn constructor + midiin = new RtMidiIn(); + + // Call function to select port. + if ( chooseMidiPort( midiin ) == false ) goto cleanup; + + // Set our callback function. This should be done immediately after + // opening the port to avoid having incoming messages written to the + // queue instead of sent to the callback function. + midiin->setCallback( &mycallback ); + + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, false, false ); + + std::cout << "\nReading MIDI input ... press to quit.\n"; + char input; + std::cin.get(input); + + } catch ( RtMidiError &error ) { + error.printMessage(); + } + + cleanup: + + delete midiin; + + return 0; +} + +bool chooseMidiPort( RtMidiIn *rtmidi ) +{ + std::cout << "\nWould you like to open a virtual input port? [y/N] "; + + std::string keyHit; + std::getline( std::cin, keyHit ); + if ( keyHit == "y" ) { + rtmidi->openVirtualPort(); + return true; + } + + std::string portName; + unsigned int i = 0, nPorts = rtmidi->getPortCount(); + if ( nPorts == 0 ) { + std::cout << "No input ports available!" << std::endl; + return false; + } + + if ( nPorts == 1 ) { + std::cout << "\nOpening " << rtmidi->getPortName() << std::endl; + } + else { + for ( i=0; igetPortName(i); + std::cout << " Input port #" << i << ": " << portName << '\n'; + } + + do { + std::cout << "\nChoose a port number: "; + std::cin >> i; + } while ( i >= nPorts ); + std::getline( std::cin, keyHit ); // used to clear out stdin + } + + rtmidi->openPort( i ); + + return true; +} diff --git a/extern/rtmidi/tests/cmidiin.dsp b/extern/rtmidi/tests/cmidiin.dsp new file mode 100644 index 00000000..55fcbe68 --- /dev/null +++ b/extern/rtmidi/tests/cmidiin.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="cmidiin" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=cmidiin - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "cmidiin.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "cmidiin.mak" CFG="cmidiin - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cmidiin - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "cmidiin - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cmidiin - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "cmidiin___Win32_Release" +# PROP BASE Intermediate_Dir "cmidiin___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "cmidiin - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "cmidiin___Win32_Debug" +# PROP BASE Intermediate_Dir "cmidiin___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "cmidiin - Win32 Release" +# Name "cmidiin - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\cmidiin.cpp +# End Source File +# Begin Source File + +SOURCE=..\RtMidi.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\RtMidi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/extern/rtmidi/tests/midiclock.cpp b/extern/rtmidi/tests/midiclock.cpp new file mode 100644 index 00000000..1d1d8ed8 --- /dev/null +++ b/extern/rtmidi/tests/midiclock.cpp @@ -0,0 +1,231 @@ +//*****************************************// +// midiclock.cpp +// +// Simple program to test MIDI clock sync. Run midiclock_in in one +// console and midiclock_out in the other, make sure to choose +// options that connect the clocks between programs on your platform. +// +// (C)2016 Refer to README.md in this archive for copyright. +// +//*****************************************// + +#include +#include +#include "RtMidi.h" + +// Platform-dependent sleep routines. +#if defined(WIN32) + #include + #define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants + #include + #define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + +// These functions should be embedded in a try/catch block in case of +// an exception. It offers the user a choice of MIDI ports to open. +// It returns false if there are no ports available. +bool chooseInputPort( RtMidiIn *rtmidi ); +bool chooseOutputPort( RtMidiOut *rtmidi ); + +void mycallback( double deltatime, std::vector< unsigned char > *message, void *user ) +{ + unsigned int *clock_count = reinterpret_cast(user); + + // Ignore longer messages + if (message->size() != 1) + return; + + unsigned int msg = message->at(0); + if (msg == 0xFA) + std::cout << "START received" << std::endl; + if (msg == 0xFB) + std::cout << "CONTINUE received" << std::endl; + if (msg == 0xFC) + std::cout << "STOP received" << std::endl; + if (msg == 0xF8) { + if (++*clock_count == 24) { + double bpm = 60.0 / 24.0 / deltatime; + std::cout << "One beat, estimated BPM = " << bpm <setCallback( &mycallback, &clock_count ); + + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, false, false ); + + std::cout << "\nReading MIDI input ... press to quit.\n"; + char input; + std::cin.get(input); + + } catch ( RtMidiError &error ) { + error.printMessage(); + } + + cleanup: + + delete midiin; + + return 0; +} + +int clock_out() +{ + RtMidiOut *midiout = 0; + std::vector message; + int sleep_ms = 0, k = 0, j = 0; + + // RtMidiOut constructor + try { + midiout = new RtMidiOut(); + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + // Call function to select port. + try { + if ( chooseOutputPort( midiout ) == false ) goto cleanup; + } + catch ( RtMidiError &error ) { + error.printMessage(); + goto cleanup; + } + + // Period in ms = 100 BPM + // 100*24 ticks / 1 minute, so (60*1000) / (100*24) = 25 ms / tick + sleep_ms = 25; + std::cout << "Generating clock at " + << (60.0 / 24.0 / sleep_ms * 1000.0) + << " BPM." << std::endl; + + // Send out a series of MIDI clock messages. + // MIDI start + message.clear(); + message.push_back( 0xFA ); + midiout->sendMessage( &message ); + std::cout << "MIDI start" << std::endl; + + for (j=0; j < 8; j++) + { + if (j > 0) + { + // MIDI continue + message.clear(); + message.push_back( 0xFB ); + midiout->sendMessage( &message ); + std::cout << "MIDI continue" << std::endl; + } + + for (k=0; k < 96; k++) { + // MIDI clock + message.clear(); + message.push_back( 0xF8 ); + midiout->sendMessage( &message ); + if (k % 24 == 0) + std::cout << "MIDI clock (one beat)" << std::endl; + SLEEP( sleep_ms ); + } + + // MIDI stop + message.clear(); + message.push_back( 0xFC ); + midiout->sendMessage( &message ); + std::cout << "MIDI stop" << std::endl; + SLEEP( 500 ); + } + + // MIDI stop + message.clear(); + message.push_back( 0xFC ); + midiout->sendMessage( &message ); + std::cout << "MIDI stop" << std::endl; + + SLEEP( 500 ); + + std::cout << "Done!" << std::endl; + + // Clean up + cleanup: + delete midiout; + + return 0; +} + +int main( int, const char *argv[] ) +{ + std::string prog(argv[0]); + if (prog.find("midiclock_in") != prog.npos) { + clock_in(); + } + else if (prog.find("midiclock_out") != prog.npos) { + clock_out(); + } + else { + std::cout << "Don't know what to do as " << prog << std::endl; + } + return 0; +} + +template +bool choosePort( RT *rtmidi, const char *dir ) +{ + std::string portName; + unsigned int i = 0, nPorts = rtmidi->getPortCount(); + if ( nPorts == 0 ) { + std::cout << "No " << dir << " ports available!" << std::endl; + return false; + } + + if ( nPorts == 1 ) { + std::cout << "\nOpening " << rtmidi->getPortName() << std::endl; + } + else { + for ( i=0; igetPortName(i); + std::cout << " " << dir << " port #" << i << ": " << portName << '\n'; + } + + do { + std::cout << "\nChoose a port number: "; + std::cin >> i; + } while ( i >= nPorts ); + } + + std::cout << "\n"; + rtmidi->openPort( i ); + + return true; +} + +bool chooseInputPort( RtMidiIn *rtmidi ) +{ + return choosePort( rtmidi, "input" ); +} + +bool chooseOutputPort( RtMidiOut *rtmidi ) +{ + return choosePort( rtmidi, "output" ); +} diff --git a/extern/rtmidi/tests/midiout.cpp b/extern/rtmidi/tests/midiout.cpp new file mode 100644 index 00000000..ce1860d6 --- /dev/null +++ b/extern/rtmidi/tests/midiout.cpp @@ -0,0 +1,146 @@ +//*****************************************// +// midiout.cpp +// by Gary Scavone, 2003-2004. +// +// Simple program to test MIDI output. +// +//*****************************************// + +#include +#include +#include "RtMidi.h" + +// Platform-dependent sleep routines. +#if defined(WIN32) + #include + #define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants + #include + #define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + +// This function should be embedded in a try/catch block in case of +// an exception. It offers the user a choice of MIDI ports to open. +// It returns false if there are no ports available. +bool chooseMidiPort( RtMidiOut *rtmidi ); + +int main( void ) +{ + RtMidiOut *midiout = 0; + std::vector message; + + // RtMidiOut constructor + try { + midiout = new RtMidiOut(); + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + // Call function to select port. + try { + if ( chooseMidiPort( midiout ) == false ) goto cleanup; + } + catch ( RtMidiError &error ) { + error.printMessage(); + goto cleanup; + } + + // Send out a series of MIDI messages. + + // Program change: 192, 5 + message.push_back( 192 ); + message.push_back( 5 ); + midiout->sendMessage( &message ); + + SLEEP( 500 ); + + message[0] = 0xF1; + message[1] = 60; + midiout->sendMessage( &message ); + + // Control Change: 176, 7, 100 (volume) + message[0] = 176; + message[1] = 7; + message.push_back( 100 ); + midiout->sendMessage( &message ); + + // Note On: 144, 64, 90 + message[0] = 144; + message[1] = 64; + message[2] = 90; + midiout->sendMessage( &message ); + + SLEEP( 500 ); + + // Note Off: 128, 64, 40 + message[0] = 128; + message[1] = 64; + message[2] = 40; + midiout->sendMessage( &message ); + + SLEEP( 500 ); + + // Control Change: 176, 7, 40 + message[0] = 176; + message[1] = 7; + message[2] = 40; + midiout->sendMessage( &message ); + + SLEEP( 500 ); + + // Sysex: 240, 67, 4, 3, 2, 247 + message[0] = 240; + message[1] = 67; + message[2] = 4; + message.push_back( 3 ); + message.push_back( 2 ); + message.push_back( 247 ); + midiout->sendMessage( &message ); + + // Clean up + cleanup: + delete midiout; + + return 0; +} + +bool chooseMidiPort( RtMidiOut *rtmidi ) +{ + std::cout << "\nWould you like to open a virtual output port? [y/N] "; + + std::string keyHit; + std::getline( std::cin, keyHit ); + if ( keyHit == "y" ) { + rtmidi->openVirtualPort(); + return true; + } + + std::string portName; + unsigned int i = 0, nPorts = rtmidi->getPortCount(); + if ( nPorts == 0 ) { + std::cout << "No output ports available!" << std::endl; + return false; + } + + if ( nPorts == 1 ) { + std::cout << "\nOpening " << rtmidi->getPortName() << std::endl; + } + else { + for ( i=0; igetPortName(i); + std::cout << " Output port #" << i << ": " << portName << '\n'; + } + + do { + std::cout << "\nChoose a port number: "; + std::cin >> i; + } while ( i >= nPorts ); + } + + std::cout << "\n"; + rtmidi->openPort( i ); + + return true; +} diff --git a/extern/rtmidi/tests/midiout.dsp b/extern/rtmidi/tests/midiout.dsp new file mode 100644 index 00000000..48e5811c --- /dev/null +++ b/extern/rtmidi/tests/midiout.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="midiout" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=midiout - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "midiout.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "midiout.mak" CFG="midiout - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "midiout - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "midiout - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "midiout - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "midiout___Win32_Release" +# PROP BASE Intermediate_Dir "midiout___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "midiout - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "midiout___Win32_Debug" +# PROP BASE Intermediate_Dir "midiout___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "midiout - Win32 Release" +# Name "midiout - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\midiout.cpp +# End Source File +# Begin Source File + +SOURCE=..\RtMidi.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\RtMidi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/extern/rtmidi/tests/midiprobe.cpp b/extern/rtmidi/tests/midiprobe.cpp new file mode 100644 index 00000000..ad967e25 --- /dev/null +++ b/extern/rtmidi/tests/midiprobe.cpp @@ -0,0 +1,76 @@ +// midiprobe.cpp +// +// Simple program to check MIDI inputs and outputs. +// +// by Gary Scavone, 2003-2012. + +#include +#include +#include +#include "RtMidi.h" + +int main() +{ + // Create an api map. + std::map apiMap; + apiMap[RtMidi::MACOSX_CORE] = "OS-X CoreMIDI"; + apiMap[RtMidi::WINDOWS_MM] = "Windows MultiMedia"; + apiMap[RtMidi::UNIX_JACK] = "Jack Client"; + apiMap[RtMidi::LINUX_ALSA] = "Linux ALSA"; + apiMap[RtMidi::RTMIDI_DUMMY] = "RtMidi Dummy"; + + std::vector< RtMidi::Api > apis; + RtMidi :: getCompiledApi( apis ); + + std::cout << "\nCompiled APIs:\n"; + for ( unsigned int i=0; igetCurrentApi() ] << std::endl; + + // Check inputs. + unsigned int nPorts = midiin->getPortCount(); + std::cout << "\nThere are " << nPorts << " MIDI input sources available.\n"; + + for ( unsigned i=0; igetPortName(i); + std::cout << " Input Port #" << i << ": " << portName << '\n'; + } + + // RtMidiOut constructor ... exception possible + midiout = new RtMidiOut(apis[i]); + + std::cout << "\nCurrent output API: " << apiMap[ midiout->getCurrentApi() ] << std::endl; + + // Check outputs. + nPorts = midiout->getPortCount(); + std::cout << "\nThere are " << nPorts << " MIDI output ports available.\n"; + + for ( unsigned i=0; igetPortName(i); + std::cout << " Output Port #" << i << ": " << portName << std::endl; + } + std::cout << std::endl; + + } catch ( RtMidiError &error ) { + error.printMessage(); + } + + delete midiin; + delete midiout; + } + + return 0; +} diff --git a/extern/rtmidi/tests/midiprobe.dsp b/extern/rtmidi/tests/midiprobe.dsp new file mode 100644 index 00000000..6a5eeaa4 --- /dev/null +++ b/extern/rtmidi/tests/midiprobe.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="midiprobe" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=midiprobe - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "midiprobe.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "midiprobe.mak" CFG="midiprobe - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "midiprobe - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "midiprobe - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "midiprobe - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "midiprobe___Win32_Release" +# PROP BASE Intermediate_Dir "midiprobe___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "midiprobe - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "midiprobe___Win32_Debug" +# PROP BASE Intermediate_Dir "midiprobe___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "midiprobe - Win32 Release" +# Name "midiprobe - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\midiprobe.cpp +# End Source File +# Begin Source File + +SOURCE=..\RtMidi.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\RtMidi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/extern/rtmidi/tests/qmidiin.cpp b/extern/rtmidi/tests/qmidiin.cpp new file mode 100644 index 00000000..3c21a864 --- /dev/null +++ b/extern/rtmidi/tests/qmidiin.cpp @@ -0,0 +1,98 @@ +//*****************************************// +// qmidiin.cpp +// by Gary Scavone, 2003-2004. +// +// Simple program to test MIDI input and +// retrieval from the queue. +// +//*****************************************// + +#include +#include +#include +#include "RtMidi.h" + +// Platform-dependent sleep routines. +#if defined(WIN32) + #include + #define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants + #include + #define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + +bool done; +static void finish( int /*ignore*/ ){ done = true; } + +void usage( void ) { + // Error function in case of incorrect command-line + // argument specifications. + std::cout << "\nusage: qmidiin \n"; + std::cout << " where port = the device to use (first / default = 0).\n\n"; + exit( 0 ); +} + +int main( int argc, char *argv[] ) +{ + RtMidiIn *midiin = 0; + std::vector message; + int nBytes, i; + double stamp; + + // Minimal command-line check. + if ( argc > 2 ) usage(); + + // RtMidiIn constructor + try { + midiin = new RtMidiIn(); + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + // Check available ports vs. specified. + unsigned int port = 0; + unsigned int nPorts = midiin->getPortCount(); + if ( argc == 2 ) port = (unsigned int) atoi( argv[1] ); + if ( port >= nPorts ) { + delete midiin; + std::cout << "Invalid port specifier!\n"; + usage(); + } + + try { + midiin->openPort( port ); + } + catch ( RtMidiError &error ) { + error.printMessage(); + goto cleanup; + } + + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, false, false ); + + // Install an interrupt handler function. + done = false; + (void) signal(SIGINT, finish); + + // Periodically check input queue. + std::cout << "Reading MIDI from port " << midiin->getPortName() << " ... quit with Ctrl-C.\n"; + while ( !done ) { + stamp = midiin->getMessage( &message ); + nBytes = message.size(); + for ( i=0; i 0 ) + std::cout << "stamp = " << stamp << std::endl; + + // Sleep for 10 milliseconds. + SLEEP( 10 ); + } + + // Clean up + cleanup: + delete midiin; + + return 0; +} diff --git a/extern/rtmidi/tests/qmidiin.dsp b/extern/rtmidi/tests/qmidiin.dsp new file mode 100644 index 00000000..77f48186 --- /dev/null +++ b/extern/rtmidi/tests/qmidiin.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="qmidiin" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=qmidiin - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "qmidiin.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "qmidiin.mak" CFG="qmidiin - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "qmidiin - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "qmidiin - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "qmidiin - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "qmidiin___Win32_Release" +# PROP BASE Intermediate_Dir "qmidiin___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "qmidiin - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "qmidiin___Win32_Debug" +# PROP BASE Intermediate_Dir "qmidiin___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "qmidiin - Win32 Release" +# Name "qmidiin - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\qmidiin.cpp +# End Source File +# Begin Source File + +SOURCE=..\RtMidi.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\RtMidi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/extern/rtmidi/tests/sysextest.cpp b/extern/rtmidi/tests/sysextest.cpp new file mode 100644 index 00000000..20e178df --- /dev/null +++ b/extern/rtmidi/tests/sysextest.cpp @@ -0,0 +1,152 @@ +//*****************************************// +// sysextest.cpp +// by Gary Scavone, 2003-2005. +// +// Simple program to test MIDI sysex sending and receiving. +// +//*****************************************// + +#include +#include +#include +#include "RtMidi.h" + +void usage( void ) { + std::cout << "\nuseage: sysextest N\n"; + std::cout << " where N = length of sysex data to send / receive.\n\n"; + exit( 0 ); +} + +// Platform-dependent sleep routines. +#if defined(WIN32) + #include + #define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants + #include + #define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + +// This function should be embedded in a try/catch block in case of +// an exception. It offers the user a choice of MIDI ports to open. +// It returns false if there are no ports available. +bool chooseMidiPort( RtMidi *rtmidi ); + +void mycallback( double deltatime, std::vector< unsigned char > *message, void * /*userData*/ ) +{ + unsigned int nBytes = message->size(); + for ( unsigned int i=0; i 0 ) + std::cout << "# of bytes = " << nBytes << ", stamp = " << deltatime << std::endl; +} + +int main( int argc, char *argv[] ) +{ + RtMidiOut *midiout = 0; + RtMidiIn *midiin = 0; + std::vector message; + unsigned int i, nBytes; + + // Minimal command-line check. + if ( argc != 2 ) usage(); + nBytes = (unsigned int) atoi( argv[1] ); + + // RtMidiOut and RtMidiIn constructors + try { + midiout = new RtMidiOut(); + midiin = new RtMidiIn(); + } + catch ( RtMidiError &error ) { + error.printMessage(); + goto cleanup; + } + + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, true, true ); + + try { + if ( chooseMidiPort( midiin ) == false ) goto cleanup; + if ( chooseMidiPort( midiout ) == false ) goto cleanup; + + midiin->setCallback( &mycallback ); + + message.push_back( 0xF6 ); + midiout->sendMessage( &message ); + SLEEP( 500 ); // pause a little + + // Create a long sysex message of numbered bytes and send it out ... twice. + for ( int n=0; n<2; n++ ) { + message.clear(); + message.push_back( 240 ); + for ( i=0; isendMessage( &message ); + + SLEEP( 500 ); // pause a little + } + } + catch ( RtMidiError &error ) { + error.printMessage(); + goto cleanup; + } + + // Clean up + cleanup: + delete midiout; + delete midiin; + + return 0; +} + +bool chooseMidiPort( RtMidi *rtmidi ) +{ + bool isInput = false; + if ( typeid( *rtmidi ) == typeid( RtMidiIn ) ) + isInput = true; + + if ( isInput ) + std::cout << "\nWould you like to open a virtual input port? [y/N] "; + else + std::cout << "\nWould you like to open a virtual output port? [y/N] "; + + std::string keyHit; + std::getline( std::cin, keyHit ); + if ( keyHit == "y" ) { + rtmidi->openVirtualPort(); + return true; + } + + std::string portName; + unsigned int i = 0, nPorts = rtmidi->getPortCount(); + if ( nPorts == 0 ) { + if ( isInput ) + std::cout << "No input ports available!" << std::endl; + else + std::cout << "No output ports available!" << std::endl; + return false; + } + + if ( nPorts == 1 ) { + std::cout << "\nOpening " << rtmidi->getPortName() << std::endl; + } + else { + for ( i=0; igetPortName(i); + if ( isInput ) + std::cout << " Input port #" << i << ": " << portName << '\n'; + else + std::cout << " Output port #" << i << ": " << portName << '\n'; + } + + do { + std::cout << "\nChoose a port number: "; + std::cin >> i; + } while ( i >= nPorts ); + } + + std::cout << std::endl; + rtmidi->openPort( i ); + + return true; +} diff --git a/extern/rtmidi/tests/sysextest.dsp b/extern/rtmidi/tests/sysextest.dsp new file mode 100644 index 00000000..0af5bf34 --- /dev/null +++ b/extern/rtmidi/tests/sysextest.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="sysextest" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=sysextest - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "sysextest.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "sysextest.mak" CFG="sysextest - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "sysextest - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "sysextest - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "sysextest - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "sysextest___Win32_Release" +# PROP BASE Intermediate_Dir "sysextest___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GR /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "sysextest - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "sysextest___Win32_Debug" +# PROP BASE Intermediate_Dir "sysextest___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GR /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_MM__" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "sysextest - Win32 Release" +# Name "sysextest - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\RtMidi.cpp +# End Source File +# Begin Source File + +SOURCE=.\sysextest.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\RtMidi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/extern/rtmidi/tests/testcapi.c b/extern/rtmidi/tests/testcapi.c new file mode 100644 index 00000000..e4cc757a --- /dev/null +++ b/extern/rtmidi/tests/testcapi.c @@ -0,0 +1,26 @@ + +#include +#include "rtmidi_c.h" + +/* Test that the C API for RtMidi is working. */ + +struct RtMidiWrapper *midiin; +struct RtMidiWrapper *midiout; + +int main() { + if ((midiin = rtmidi_in_create_default())) { + unsigned int ports = rtmidi_get_port_count(midiin); + printf("MIDI input ports found: %u\n", ports); + rtmidi_close_port(midiin); + rtmidi_in_free(midiin); + } + + if ((midiout = rtmidi_out_create_default())) { + unsigned int ports = rtmidi_get_port_count(midiout); + printf("MIDI output ports found: %u\n", ports); + rtmidi_close_port(midiout); + rtmidi_out_free(midiout); + } + + return 0; +} diff --git a/src/audio/rtmidi.h b/src/audio/rtmidi.h new file mode 100644 index 00000000..c0741f3e --- /dev/null +++ b/src/audio/rtmidi.h @@ -0,0 +1 @@ +#include "../../extern/rtmidi/RtMidi.h" \ No newline at end of file diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 86479a0b..bb2074bc 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -1,6 +1,7 @@ #ifndef _TAAUDIO_H #define _TAAUDIO_H #include "../ta-utils.h" +#include struct SampleRateChangeEvent { double rate; @@ -79,4 +80,73 @@ class TAAudio { virtual ~TAAudio(); }; + +enum TAMidiMessageTypes { + TA_MIDI_NOTE_OFF=0x80, + TA_MIDI_NOTE_ON=0x90, + TA_MIDI_AFTERTOUCH=0xa0, + TA_MIDI_CONTROL=0xb0, + TA_MIDI_PROGRAM=0xc0, + TA_MIDI_CHANNEL_AFTERTOUCH=0xd0, + TA_MIDI_PITCH_BEND=0xe0, + TA_MIDI_SYSEX=0xf0, + TA_MIDI_MTC_FRAME=0xf1, + TA_MIDI_POSITION=0xf2, + TA_MIDI_SONG_SELECT=0xf3, + TA_MIDI_TUNE_REQUEST=0xf6, + TA_MIDI_SYSEX_END=0xf7, + TA_MIDI_CLOCK=0xf8, + TA_MIDI_MACHINE_PLAY=0xfa, + TA_MIDI_MACHINE_RESUME=0xfb, + TA_MIDI_MACHINE_STOP=0xfc, + TA_MIDI_KEEPALIVE=0xfe, + TA_MIDI_RESET=0xff +}; + +struct TAMidiMessage { + unsigned char type; + union { + struct { + unsigned char note, vol; + } note; + struct { + unsigned char which, val; + } control; + unsigned char patch; + unsigned char pressure; + struct { + unsigned char low, high; + } pitch; + struct { + unsigned int vendor; + } sysEx; + unsigned char timeCode; + struct { + unsigned char low, high; + } position; + unsigned char song; + } data; + unsigned char* sysExData; + size_t sysExLen; + + void submitSysEx(std::vector data); + void done(); +}; + +class TAMidiIn { + public: + bool next(TAMidiMessage& where); +}; + +class TAMidiOut { + public: + bool send(TAMidiMessage& what); +}; + +class TAMidi { + std::vector in; + std::vector out; + + +}; #endif