From 1fd900263b502479b6e81ee378fc2f36ad383f50 Mon Sep 17 00:00:00 2001 From: Gary Scavone Date: Tue, 16 Nov 2021 21:28:04 -0500 Subject: [PATCH] New versions of RtAudio and RtMidi in preparation for new release. --- include/RtAudio.h | 268 +++++++-------- include/RtMidi.h | 35 +- src/RtAudio.cpp | 831 ++++++++++++++++++++++++++++++++-------------- src/RtMidi.cpp | 703 +++++++++++++++++++++++++++++++++------ 4 files changed, 1342 insertions(+), 495 deletions(-) diff --git a/include/RtAudio.h b/include/RtAudio.h index aa980b1..1aa42e0 100644 --- a/include/RtAudio.h +++ b/include/RtAudio.h @@ -46,7 +46,7 @@ #ifndef __RTAUDIO_H #define __RTAUDIO_H -#define RTAUDIO_VERSION "5.1.0" +#define RTAUDIO_VERSION "5.2.0" #if defined _WIN32 || defined __CYGWIN__ #if defined(RTAUDIO_EXPORT) @@ -226,12 +226,12 @@ class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error 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. */ + MEMORY_ERROR, /*!< An error occurred during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ - DRIVER_ERROR, /*!< A system driver error occured. */ - SYSTEM_ERROR, /*!< A system error occured. */ - THREAD_ERROR /*!< A thread error occured. */ + DRIVER_ERROR, /*!< A system driver error occurred. */ + SYSTEM_ERROR, /*!< A system error occurred. */ + THREAD_ERROR /*!< A thread error occurred. */ }; //! The constructor. @@ -299,30 +299,21 @@ class RTAUDIO_DLL_PUBLIC RtAudio struct DeviceInfo { bool probed; /*!< true if the device capabilities were successfully probed. */ std::string name; /*!< Character string device identifier. */ - unsigned int outputChannels; /*!< Maximum output channels supported by device. */ - unsigned int inputChannels; /*!< Maximum input channels supported by device. */ - unsigned int duplexChannels; /*!< Maximum simultaneous input/output channels supported by device. */ - bool isDefaultOutput; /*!< true if this is the default output device. */ - bool isDefaultInput; /*!< true if this is the default input device. */ + unsigned int outputChannels{}; /*!< Maximum output channels supported by device. */ + unsigned int inputChannels{}; /*!< Maximum input channels supported by device. */ + unsigned int duplexChannels{}; /*!< Maximum simultaneous input/output channels supported by device. */ + bool isDefaultOutput{false}; /*!< true if this is the default output device. */ + bool isDefaultInput{false}; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ - unsigned int preferredSampleRate; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ - RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ - - // Default constructor. - DeviceInfo() - :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), - isDefaultOutput(false), isDefaultInput(false), preferredSampleRate(0), nativeFormats(0) {} + unsigned int preferredSampleRate{}; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ + RtAudioFormat nativeFormats{}; /*!< Bit mask of supported data formats. */ }; - //! The structure for specifying input or ouput stream parameters. + //! The structure for specifying input or output stream parameters. struct StreamParameters { - unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ - unsigned int nChannels; /*!< Number of channels. */ - unsigned int firstChannel; /*!< First channel index on device (default = 0). */ - - // Default constructor. - StreamParameters() - : deviceId(0), nChannels(0), firstChannel(0) {} + unsigned int deviceId{}; /*!< Device index (0 to getDeviceCount() - 1). */ + unsigned int nChannels{}; /*!< Number of channels. */ + unsigned int firstChannel{}; /*!< First channel index on device (default = 0). */ }; //! The structure for specifying stream options. @@ -383,14 +374,10 @@ class RTAUDIO_DLL_PUBLIC RtAudio RtAudio with Jack, each instance must have a unique client name. */ struct StreamOptions { - RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ - unsigned int numberOfBuffers; /*!< Number of stream buffers. */ + RtAudioStreamFlags flags{}; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ + unsigned int numberOfBuffers{}; /*!< Number of stream buffers. */ std::string streamName; /*!< A stream name (currently used only in Jack). */ - int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ - - // Default constructor. - StreamOptions() - : flags(0), numberOfBuffers(0), priority(0) {} + int priority{}; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ }; //! A static function to determine the current RtAudio version. @@ -527,7 +514,7 @@ class RTAUDIO_DLL_PUBLIC RtAudio lowest allowable value is used. The actual value used is returned via the structure argument. The parameter is API dependent. \param errorCallback A client-defined function that will be invoked - when an error has occured. + when an error has occurred. */ void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, @@ -616,7 +603,7 @@ class RTAUDIO_DLL_PUBLIC RtAudio }; // Operating system dependent thread functionality. -#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) +#if defined(_WIN32) || defined(__CYGWIN__) #ifndef NOMINMAX #define NOMINMAX @@ -628,18 +615,22 @@ class RTAUDIO_DLL_PUBLIC RtAudio typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; -#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) +#else + // Using pthread library for various flavors of unix. #include typedef pthread_t ThreadHandle; typedef pthread_mutex_t StreamMutex; -#else // Setup for "dummy" behavior +#endif + +// Setup for "dummy" behavior if no apis specified. +#if !(defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) \ + || defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) \ + || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__)) #define __RTAUDIO_DUMMY__ - typedef int ThreadHandle; - typedef int StreamMutex; #endif @@ -647,19 +638,15 @@ class RTAUDIO_DLL_PUBLIC RtAudio // between the private RtAudio stream structure and global callback // handling functions. struct CallbackInfo { - void *object; // Used as a "this" pointer. - ThreadHandle thread; - void *callback; - void *userData; - void *errorCallback; - void *apiInfo; // void pointer for API specific callback information - bool isRunning; - bool doRealtime; - int priority; - - // Default constructor. - CallbackInfo() - :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false), priority(0) {} + void *object{}; // Used as a "this" pointer. + ThreadHandle thread{}; + void *callback{}; + void *userData{}; + void *errorCallback{}; + void *apiInfo{}; // void pointer for API specific callback information + bool isRunning{false}; + bool doRealtime{false}; + int priority{}; }; // **************************************************************** // @@ -686,9 +673,9 @@ class S24 { S24() {} S24& operator = ( const int& i ) { - c3[0] = (i & 0x000000ff); - c3[1] = (i & 0x0000ff00) >> 8; - c3[2] = (i & 0x00ff0000) >> 16; + c3[0] = (unsigned char)(i & 0x000000ff); + c3[1] = (unsigned char)((i & 0x0000ff00) >> 8); + c3[2] = (unsigned char)((i & 0x00ff0000) >> 16); return *this; } @@ -895,20 +882,20 @@ public: RtApiCore(); ~RtApiCore(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - unsigned int getDefaultOutputDevice( void ); - unsigned int getDefaultInputDevice( void ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::MACOSX_CORE; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! bool callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ); @@ -918,7 +905,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; static const char* getErrorCode( OSStatus code ); }; @@ -932,18 +919,18 @@ public: RtApiJack(); ~RtApiJack(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::UNIX_JACK; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! bool callbackEvent( unsigned long nframes ); private: @@ -951,7 +938,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; bool shouldAutoconnect_; }; @@ -966,18 +953,20 @@ public: RtApiAsio(); ~RtApiAsio(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_ASIO; } + unsigned int getDeviceCount( void ) override; + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! bool callbackEvent( long bufferIndex ); private: @@ -988,7 +977,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1001,20 +990,20 @@ public: RtApiDs(); ~RtApiDs(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } - unsigned int getDeviceCount( void ); - unsigned int getDefaultOutputDevice( void ); - unsigned int getDefaultInputDevice( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_DS; } + unsigned int getDeviceCount( void ) override; + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: @@ -1026,7 +1015,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1041,15 +1030,13 @@ public: RtApiWasapi(); virtual ~RtApiWasapi(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - unsigned int getDefaultOutputDevice( void ); - unsigned int getDefaultInputDevice( void ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_WASAPI; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; private: bool coInitialized_; @@ -1058,7 +1045,7 @@ private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, - RtAudio::StreamOptions* options ); + RtAudio::StreamOptions* options ) override; static DWORD WINAPI runWasapiThread( void* wasapiPtr ); static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); @@ -1076,18 +1063,18 @@ public: RtApiAlsa(); ~RtApiAlsa(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_ALSA; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: @@ -1097,7 +1084,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1108,28 +1095,27 @@ class RtApiPulse: public RtApi { public: ~RtApiPulse(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_PULSE; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: - std::vector devices_; - void saveDeviceInfo( void ); + void collectDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1142,18 +1128,18 @@ public: RtApiOss(); ~RtApiOss(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_OSS; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: @@ -1161,7 +1147,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1173,20 +1159,20 @@ class RtApiDummy: public RtApi public: RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } - RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } - unsigned int getDeviceCount( void ) { return 0; } - RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } - void closeStream( void ) {} - void startStream( void ) {} - void stopStream( void ) {} - void abortStream( void ) {} + RtAudio::Api getCurrentApi( void ) override { return RtAudio::RTAUDIO_DUMMY; } + unsigned int getDeviceCount( void ) override { return 0; } + RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) override { RtAudio::DeviceInfo info; return info; } + void closeStream( void ) override {} + void startStream( void ) override {} + void stopStream( void ) override {} + void abortStream( void ) override {} private: bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, - RtAudio::StreamOptions * /*options*/ ) { return false; } + RtAudio::StreamOptions * /*options*/ ) override { return false; } }; #endif diff --git a/include/RtMidi.h b/include/RtMidi.h index 8a618d0..a6f5b79 100644 --- a/include/RtMidi.h +++ b/include/RtMidi.h @@ -58,13 +58,14 @@ #endif #endif -#define RTMIDI_VERSION "4.0.0" +#define RTMIDI_VERSION "5.0.0" #include #include #include #include + /************************************************************************/ /*! \class RtMidiError \brief Exception handling class for RtMidi. @@ -132,6 +133,8 @@ 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. */ @@ -140,6 +143,7 @@ class RTMIDI_DLL_PUBLIC RtMidi 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. */ }; @@ -213,6 +217,10 @@ class RTMIDI_DLL_PUBLIC RtMidi RtMidi(); virtual ~RtMidi(); MidiApi *rtapi_; + + /* Make the class non-copyable */ + RtMidi(RtMidi& other) = delete; + RtMidi& operator=(RtMidi& other) = delete; }; /**********************************************************************/ @@ -248,7 +256,6 @@ class RTMIDI_DLL_PUBLIC RtMidi class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi { public: - //! User callback function type definition. typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData ); @@ -274,6 +281,8 @@ class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi 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(); @@ -371,6 +380,19 @@ class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi */ 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 ); }; @@ -403,6 +425,8 @@ class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi 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(); @@ -523,6 +547,7 @@ protected: RtMidiErrorCallback errorCallback_; bool firstErrorOccurred_; void *errorCallbackUserData_; + }; class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi @@ -535,6 +560,7 @@ class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi 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. @@ -576,11 +602,13 @@ class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi 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) {} + userCallback(0), userData(0), continueSysex(false), bufferSize(1024), bufferCount(4) {} }; protected: @@ -614,6 +642,7 @@ inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return r 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 ); } diff --git a/src/RtAudio.cpp b/src/RtAudio.cpp index ce3d0b2..40fb6a9 100644 --- a/src/RtAudio.cpp +++ b/src/RtAudio.cpp @@ -39,7 +39,7 @@ */ /************************************************************************/ -// RtAudio: Version 5.1.0 +// RtAudio: Version 5.2.0 #include "RtAudio.h" #include @@ -56,7 +56,7 @@ const unsigned int RtApi::SAMPLE_RATES[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; -#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) +#if defined(_WIN32) || defined(__CYGWIN__) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) @@ -64,12 +64,17 @@ const unsigned int RtApi::SAMPLE_RATES[] = { #include "tchar.h" - static std::string convertCharPointerToStdString(const char *text) + template inline + std::string convertCharPointerToStdString(const T *text); + + template<> inline + std::string convertCharPointerToStdString(const char *text) { return std::string(text); } - static std::string convertCharPointerToStdString(const wchar_t *text) + template<> inline + std::string convertCharPointerToStdString(const wchar_t *text) { int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); std::string s( length-1, '\0' ); @@ -77,15 +82,12 @@ const unsigned int RtApi::SAMPLE_RATES[] = { return s; } -#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) +#elif defined(__unix__) || defined(__APPLE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) -#else - #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions - #define MUTEX_DESTROY(A) abs(*A) // dummy definitions #endif // *************************************************** // @@ -262,7 +264,7 @@ RtAudio :: RtAudio( RtAudio::Api api ) // It should not be possible to get here because the preprocessor // definition __RTAUDIO_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in - // case something weird happens, we'll thow an error. + // case something weird happens, we'll throw an error. std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } @@ -405,13 +407,27 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, unsigned int RtApi :: getDefaultInputDevice( void ) { - // Should be implemented in subclasses if possible. + // Should be reimplemented in subclasses if necessary. + unsigned int nDevices = getDeviceCount(); + for ( unsigned int i = 0; i < nDevices; i++ ) { + if ( getDeviceInfo( i ).isDefaultInput ) { + return i; + } + } + return 0; } unsigned int RtApi :: getDefaultOutputDevice( void ) { - // Should be implemented in subclasses if possible. + // Should be reimplemented in subclasses if necessary. + unsigned int nDevices = getDeviceCount(); + for ( unsigned int i = 0; i < nDevices; i++ ) { + if ( getDeviceInfo( i ).isDefaultOutput ) { + return i; + } + } + return 0; } @@ -506,6 +522,8 @@ unsigned int RtApi :: getStreamSampleRate( void ) #if defined(__MACOSX_CORE__) +#include + // The OS X CoreAudio API is designed to use a separate callback // procedure for each of its audio devices. A single RtAudio duplex // stream using two different devices is supported here, though it @@ -1478,15 +1496,17 @@ void RtApiCore :: closeStream( void ) errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[0], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); -#else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], handle->procId[0] ); + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif + } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { @@ -1501,15 +1521,17 @@ void RtApiCore :: closeStream( void ) errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[1], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); -#else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], handle->procId[1] ); + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], callbackHandler ); + AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif + } } for ( int i=0; i<2; i++ ) { @@ -1542,15 +1564,19 @@ void RtApiCore :: startStream( void ) return; } - #if defined( HAVE_GETTIMEOFDAY ) +#if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); - #endif +#endif OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStart( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour result = AudioDeviceStart( handle->id[0], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1561,7 +1587,11 @@ void RtApiCore :: startStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStart( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour result = AudioDeviceStart( handle->id[1], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -1596,7 +1626,11 @@ void RtApiCore :: stopStream( void ) pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStop( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour result = AudioDeviceStop( handle->id[0], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1606,7 +1640,11 @@ void RtApiCore :: stopStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStop( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour result = AudioDeviceStop( handle->id[1], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -2790,13 +2828,13 @@ static long asioMessages( long selector, long value, void* message, double* opt RtApiAsio :: RtApiAsio() { - // ASIO cannot run on a multi-threaded appartment. You can call - // CoInitialize beforehand, but it must be for appartment threading + // ASIO cannot run on a multi-threaded apartment. You can call + // CoInitialize beforehand, but it must be for apartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { - errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; + errorText_ = "RtApiAsio::ASIO requires a single-threaded apartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RtAudioError::WARNING ); } coInitialized_ = true; @@ -2819,6 +2857,18 @@ unsigned int RtApiAsio :: getDeviceCount( void ) return (unsigned int) drivers.asioGetNumDev(); } +// We can only load one ASIO driver, so the default output is always the first device. +unsigned int RtApiAsio :: getDefaultOutputDevice( void ) +{ + return 0; +} + +// We can only load one ASIO driver, so the default input is always the first device. +unsigned int RtApiAsio :: getDefaultInputDevice( void ) +{ + return 0; +} + RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; @@ -3799,6 +3849,17 @@ if ( objectPtr )\ typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); +#ifndef __IAudioClient3_INTERFACE_DEFINED__ +MIDL_INTERFACE( "00000000-0000-0000-0000-000000000000" ) IAudioClient3 +{ + virtual HRESULT GetSharedModeEnginePeriod( WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32* ) = 0; + virtual HRESULT InitializeSharedAudioStream( DWORD, UINT32, WAVEFORMATEX*, LPCGUID ) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL( IAudioClient3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) +#endif +#endif + //----------------------------------------------------------------------------- // WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. @@ -4053,7 +4114,7 @@ public: #endif } - void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount, int maxOutSampleCount = -1 ) { unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; if ( _sampleRatio == 1 ) @@ -4064,7 +4125,15 @@ public: return; } - unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + unsigned int outputBufferSize = 0; + if ( maxOutSampleCount != -1 ) + { + outputBufferSize = _bytesPerSample * _channelCount * maxOutSampleCount; + } + else + { + outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + } IMFMediaBuffer* rInBuffer; IMFSample* rInSample; @@ -4486,34 +4555,6 @@ Exit: return info; } -//----------------------------------------------------------------------------- - -unsigned int RtApiWasapi::getDefaultOutputDevice( void ) -{ - for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { - if ( getDeviceInfo( i ).isDefaultOutput ) { - return i; - } - } - - return 0; -} - -//----------------------------------------------------------------------------- - -unsigned int RtApiWasapi::getDefaultInputDevice( void ) -{ - for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { - if ( getDeviceInfo( i ).isDefaultInput ) { - return i; - } - } - - return 0; -} - -//----------------------------------------------------------------------------- - void RtApiWasapi::closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { @@ -4600,17 +4641,16 @@ void RtApiWasapi::stopStream( void ) error( RtAudioError::WARNING ); return; } + if ( stream_.state == STREAM_STOPPING ) { + errorText_ = "RtApiWasapi::stopStream: The stream is already stopping."; + error( RtAudioError::WARNING ); + return; + } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; - // wait until stream thread is stopped - while( stream_.state != STREAM_STOPPED ) { - Sleep( 1 ); - } - - // Wait for the last buffer to play before stopping. - Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); + WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { @@ -4633,14 +4673,16 @@ void RtApiWasapi::abortStream( void ) error( RtAudioError::WARNING ); return; } + if ( stream_.state == STREAM_STOPPING ) { + errorText_ = "RtApiWasapi::abortStream: The stream is already stopping."; + error( RtAudioError::WARNING ); + return; + } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; - // wait until stream thread is stopped - while ( stream_.state != STREAM_STOPPED ) { - Sleep( 1 ); - } + WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { @@ -4849,7 +4891,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) - setConvertInfo( mode, 0 ); + setConvertInfo( mode, firstChannel ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); @@ -4941,7 +4983,7 @@ void RtApiWasapi::wasapiThread() // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; - unsigned long captureFlags = 0; + DWORD captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; @@ -4960,7 +5002,7 @@ void RtApiWasapi::wasapiThread() RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread - HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); + HMODULE AvrtDll = LoadLibraryW( L"AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = @@ -4985,12 +5027,37 @@ void RtApiWasapi::wasapiThread() captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); if ( !captureClient ) { - hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, - loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, - 0, - captureFormat, - NULL ); + IAudioClient3* captureAudioClient3 = nullptr; + captureAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &captureAudioClient3 ); + if ( captureAudioClient3 && !loopbackEnabled ) + { + UINT32 Ignore; + UINT32 MinPeriodInFrames; + hr = captureAudioClient3->GetSharedModeEnginePeriod( captureFormat, + &Ignore, + &Ignore, + &MinPeriodInFrames, + &Ignore ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + goto Exit; + } + + hr = captureAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + MinPeriodInFrames, + captureFormat, + NULL ); + } + else + { + hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + captureFormat, + NULL ); + } + if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; @@ -5071,12 +5138,37 @@ void RtApiWasapi::wasapiThread() renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); if ( !renderClient ) { - hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, - 0, - renderFormat, - NULL ); + IAudioClient3* renderAudioClient3 = nullptr; + renderAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &renderAudioClient3 ); + if ( renderAudioClient3 ) + { + UINT32 Ignore; + UINT32 MinPeriodInFrames; + hr = renderAudioClient3->GetSharedModeEnginePeriod( renderFormat, + &Ignore, + &Ignore, + &MinPeriodInFrames, + &Ignore ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + goto Exit; + } + + hr = renderAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + MinPeriodInFrames, + renderFormat, + NULL ); + } + else + { + hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + renderFormat, + NULL ); + } + if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; @@ -5140,18 +5232,18 @@ void RtApiWasapi::wasapiThread() if ( stream_.mode == INPUT ) { using namespace std; // for ceilf - convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } else if ( stream_.mode == OUTPUT ) { - convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } else if ( stream_.mode == DUPLEX ) { - convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), - ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + convBuffSize = std::max( ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } @@ -5177,11 +5269,6 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); - if ( captureSrRatio != 1 ) - { - // account for remainders - samplesToPull--; - } convBufferSize = 0; while ( convBufferSize < stream_.bufferSize ) @@ -5203,7 +5290,8 @@ void RtApiWasapi::wasapiThread() captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, convBuffer, samplesToPull, - convSamples ); + convSamples, + convBufferSize == 0 ? -1 : stream_.bufferSize - convBufferSize ); convBufferSize += convSamples; samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples @@ -5471,14 +5559,14 @@ Exit: CoUninitialize(); - // update stream state - stream_.state = STREAM_STOPPED; - if ( !errorText.empty() ) { errorText_ = errorText; error( errorType ); } + + // update stream state + stream_.state = STREAM_STOPPED; } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -6863,7 +6951,7 @@ void RtApiDs :: callbackEvent() if ( stream_.mode == DUPLEX ) { if ( safeReadPointer < endRead ) { if ( duplexPrerollBytes <= 0 ) { - // Pre-roll time over. Be more agressive. + // Pre-roll time over. Be more aggressive. int adjustment = endRead-safeReadPointer; handle->xrun[1] = true; @@ -7018,19 +7106,18 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, // If good device, then save its name and guid. std::string name = convertCharPointerToStdString( description ); - //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) - if ( lpguid == NULL ) - name = "Default Device"; + if ( validDevice ) { for ( unsigned int i=0; i= 201103L + :handles{nullptr, nullptr}, synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } +#else + : synchronized(false), runnable(false) { handles[0] = NULL; handles[1] = NULL; xrun[0] = false; xrun[1] = false; } +#endif }; static void *alsaCallbackHandler( void * ptr ); @@ -7148,6 +7239,13 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) char name[64]; snd_ctl_t *handle = 0; + strcpy(name, "default"); + result = snd_ctl_open( &handle, "default", 0 ); + if (result == 0) { + nDevices++; + snd_ctl_close( handle ); + } + // Count cards and devices card = -1; snd_card_next( &card ); @@ -7180,12 +7278,6 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) snd_card_next( &card ); } - result = snd_ctl_open( &handle, "default", 0 ); - if (result == 0) { - nDevices++; - snd_ctl_close( handle ); - } - return nDevices; } @@ -7195,13 +7287,21 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) info.probed = false; unsigned nDevices = 0; - int result, subdevice, card; + int result=-1, subdevice=-1, card=-1; char name[64]; snd_ctl_t *chandle = 0; + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices++ == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + } + if ( chandle ) + snd_ctl_close( chandle ); + // Count cards and devices - card = -1; - subdevice = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); @@ -7235,15 +7335,6 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_card_next( &card ); } - result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); - if ( result == 0 ) { - if ( nDevices == device ) { - strcpy( name, "default" ); - goto foundDevice; - } - nDevices++; - } - if ( nDevices == 0 ) { errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); @@ -7464,11 +7555,13 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) } // Get the device name - char *cardname; - result = snd_card_get_name( card, &cardname ); - if ( result >= 0 ) { - sprintf( name, "hw:%s,%d", cardname, subdevice ); - free( cardname ); + if (strncmp(name, "default", 7)!=0) { + char *cardname; + result = snd_card_get_name( card, &cardname ); + if ( result >= 0 ) { + sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } } info.name = name; @@ -7495,8 +7588,12 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne { #if defined(__RTAUDIO_DEBUG__) - snd_output_t *out; - snd_output_stdio_attach(&out, stderr, 0); + struct SndOutputTdealloc { + SndOutputTdealloc() : _out(NULL) { snd_output_stdio_attach(&_out, stderr, 0); } + ~SndOutputTdealloc() { snd_output_close(_out); } + operator snd_output_t*() { return _out; } + snd_output_t *_out; + } out; #endif // I'm not using the "plug" interface ... too much inconsistent behavior. @@ -7506,9 +7603,23 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne char name[64]; snd_ctl_t *chandle; - if ( options && options->flags & RTAUDIO_ALSA_USE_DEFAULT ) - snprintf(name, sizeof(name), "%s", "default"); + if ( device == 0 + || (options && options->flags & RTAUDIO_ALSA_USE_DEFAULT) ) + { + strcpy(name, "default"); + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + snd_ctl_close( chandle ); + goto foundDevice; + } + nDevices++; + } + } + else { + nDevices++; // Count cards and devices card = -1; snd_card_next( &card ); @@ -7536,17 +7647,6 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne snd_card_next( &card ); } - result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); - if ( result == 0 ) { - if ( nDevices == device ) { - strcpy( name, "default" ); - snd_ctl_close( chandle ); - goto foundDevice; - } - nDevices++; - } - snd_ctl_close( chandle ); - if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; @@ -8284,7 +8384,7 @@ void RtApiAlsa :: callbackEvent() } if ( result < (int) stream_.bufferSize ) { - // Either an error or overrun occured. + // Either an error or overrun occurred. if ( result == -EPIPE ) { snd_pcm_state_t state = snd_pcm_state( handle[1] ); if ( state == SND_PCM_STATE_XRUN ) { @@ -8354,7 +8454,7 @@ void RtApiAlsa :: callbackEvent() } if ( result < (int) stream_.bufferSize ) { - // Either an error or underrun occured. + // Either an error or underrun occurred. if ( result == -EPIPE ) { snd_pcm_state_t state = snd_pcm_state( handle[0] ); if ( state == SND_PCM_STATE_XRUN ) { @@ -8424,10 +8524,27 @@ static void *alsaCallbackHandler( void *ptr ) #include #include +#include #include +static pa_mainloop_api *rt_pa_mainloop_api = NULL; +struct PaDeviceInfo { + PaDeviceInfo() : sink_index(-1), source_index(-1) {} + int sink_index; + int source_index; + std::string sink_name; + std::string source_name; + RtAudio::DeviceInfo info; +}; +static struct { + std::vector dev; + std::string default_sink_name; + std::string default_source_name; + int default_rate; +} rt_pa_info; + static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, - 44100, 48000, 96000, 0}; + 44100, 48000, 96000, 192000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; @@ -8436,6 +8553,7 @@ struct rtaudio_pa_format_mapping_t { static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT24, PA_SAMPLE_S24LE}, {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; @@ -8449,35 +8567,219 @@ struct PulseAudioHandle { PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; +static void rt_pa_mainloop_api_quit(int ret) { + rt_pa_mainloop_api->quit(rt_pa_mainloop_api, ret); +} + +static void rt_pa_set_server_info(pa_context *context, const pa_server_info *info, void *data){ + (void)context; + (void)data; + pa_sample_spec ss; + + if (!info) { + rt_pa_mainloop_api_quit(1); + return; + } + + ss = info->sample_spec; + + rt_pa_info.default_rate = ss.rate; + rt_pa_info.default_sink_name = info->default_sink_name; + rt_pa_info.default_source_name = info->default_source_name; +} + +static void rt_pa_set_sink_info(pa_context * /*c*/, const pa_sink_info *i, + int eol, void * /*userdata*/) +{ + if (eol) return; + PaDeviceInfo inf; + inf.info.name = pa_proplist_gets(i->proplist, "device.description"); + inf.info.probed = true; + inf.info.outputChannels = i->sample_spec.channels; + inf.info.preferredSampleRate = i->sample_spec.rate; + inf.info.isDefaultOutput = (rt_pa_info.default_sink_name == i->name); + inf.sink_index = i->index; + inf.sink_name = i->name; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + inf.info.sampleRates.push_back( *sr ); + for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; + fm->rtaudio_format; ++fm ) + inf.info.nativeFormats |= fm->rtaudio_format; + for (size_t i=0; i < rt_pa_info.dev.size(); i++) + { + /* Attempt to match up sink and source records by device description. */ + if (rt_pa_info.dev[i].info.name == inf.info.name) { + rt_pa_info.dev[i].sink_index = inf.sink_index; + rt_pa_info.dev[i].sink_name = inf.sink_name; + rt_pa_info.dev[i].info.outputChannels = inf.info.outputChannels; + rt_pa_info.dev[i].info.isDefaultOutput = inf.info.isDefaultOutput; + /* Assume duplex channels are minimum of input and output channels. */ + /* Uncomment if we add support for DUPLEX + if (rt_pa_info.dev[i].source_index > -1) + (inf.info.outputChannels < rt_pa_info.dev[i].info.inputChannels) + ? inf.info.outputChannels : rt_pa_info.dev[i].info.inputChannels; + */ + return; + } + } + /* try to ensure device #0 is the default */ + if (inf.info.isDefaultOutput) + rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); + else + rt_pa_info.dev.push_back(inf); +} + +static void rt_pa_set_source_info_and_quit(pa_context * /*c*/, const pa_source_info *i, + int eol, void * /*userdata*/) +{ + if (eol) { + rt_pa_mainloop_api_quit(0); + return; + } + PaDeviceInfo inf; + inf.info.name = pa_proplist_gets(i->proplist, "device.description"); + inf.info.probed = true; + inf.info.inputChannels = i->sample_spec.channels; + inf.info.preferredSampleRate = i->sample_spec.rate; + inf.info.isDefaultInput = (rt_pa_info.default_source_name == i->name); + inf.source_index = i->index; + inf.source_name = i->name; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + inf.info.sampleRates.push_back( *sr ); + for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; + fm->rtaudio_format; ++fm ) + inf.info.nativeFormats |= fm->rtaudio_format; + + for (size_t i=0; i < rt_pa_info.dev.size(); i++) + { + /* Attempt to match up sink and source records by device description. */ + if (rt_pa_info.dev[i].info.name == inf.info.name) { + rt_pa_info.dev[i].source_index = inf.source_index; + rt_pa_info.dev[i].source_name = inf.source_name; + rt_pa_info.dev[i].info.inputChannels = inf.info.inputChannels; + rt_pa_info.dev[i].info.isDefaultInput = inf.info.isDefaultInput; + /* Assume duplex channels are minimum of input and output channels. */ + /* Uncomment if we add support for DUPLEX + if (rt_pa_info.dev[i].sink_index > -1) { + rt_pa_info.dev[i].info.duplexChannels = + (inf.info.inputChannels < rt_pa_info.dev[i].info.outputChannels) + ? inf.info.inputChannels : rt_pa_info.dev[i].info.outputChannels; + } + */ + return; + } + } + /* try to ensure device #0 is the default */ + if (inf.info.isDefaultInput) + rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); + else + rt_pa_info.dev.push_back(inf); +} + +static void rt_pa_context_state_callback(pa_context *context, void *userdata) { + (void)userdata; + + auto state = pa_context_get_state(context); + switch (state) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + rt_pa_info.dev.clear(); + pa_context_get_server_info(context, rt_pa_set_server_info, NULL); + pa_context_get_sink_info_list(context, rt_pa_set_sink_info, NULL); + pa_context_get_source_info_list(context, rt_pa_set_source_info_and_quit, NULL); + break; + + case PA_CONTEXT_TERMINATED: + rt_pa_mainloop_api_quit(0); + break; + + case PA_CONTEXT_FAILED: + default: + rt_pa_mainloop_api_quit(1); + } +} + RtApiPulse::~RtApiPulse() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } -unsigned int RtApiPulse::getDeviceCount( void ) +void RtApiPulse::collectDeviceInfo( void ) { - return 1; + pa_context *context = NULL; + pa_mainloop *m = NULL; + char *server = NULL; + int ret = 1; + + if (!(m = pa_mainloop_new())) { + errorStream_ << "RtApiPulse::DeviceInfo pa_mainloop_new() failed."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + rt_pa_mainloop_api = pa_mainloop_get_api(m); + + if (!(context = pa_context_new_with_proplist(rt_pa_mainloop_api, NULL, NULL))) { + errorStream_ << "pa_context_new() failed."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + pa_context_set_state_callback(context, rt_pa_context_state_callback, NULL); + + if (pa_context_connect(context, server, PA_CONTEXT_NOFLAGS, NULL) < 0) { + errorStream_ << "RtApiPulse::DeviceInfo pa_context_connect() failed: " + << pa_strerror(pa_context_errno(context)); + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + if (pa_mainloop_run(m, &ret) < 0) { + errorStream_ << "pa_mainloop_run() failed."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + if (ret != 0) { + errorStream_ << "could not get server info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + +quit: + if (context) + pa_context_unref(context); + + if (m) { + pa_mainloop_free(m); + } + + pa_xfree(server); } -RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) +unsigned int RtApiPulse::getDeviceCount( void ) { - RtAudio::DeviceInfo info; - info.probed = true; - info.name = "PulseAudio"; - info.outputChannels = 2; - info.inputChannels = 2; - info.duplexChannels = 2; - info.isDefaultOutput = true; - info.isDefaultInput = true; + collectDeviceInfo(); + return rt_pa_info.dev.size(); +} - for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) - info.sampleRates.push_back( *sr ); - - info.preferredSampleRate = 48000; - info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; - - return info; +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) +{ + if (rt_pa_info.dev.size()==0) + collectDeviceInfo(); + if (device < rt_pa_info.dev.size()) + return rt_pa_info.dev[device].info; + return RtAudio::DeviceInfo(); } static void *pulseaudio_callback( void * user ) @@ -8679,15 +8981,18 @@ void RtApiPulse::stopStream( void ) stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - if ( pah && pah->s_play ) { - int pa_error; - if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::stopStream: error draining output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } } } @@ -8713,15 +9018,18 @@ void RtApiPulse::abortStream( void ) stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - if ( pah && pah->s_play ) { - int pa_error; - if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } } } @@ -8738,15 +9046,51 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned long bufferBytes = 0; pa_sample_spec ss; - if ( device != 0 ) return false; - if ( mode != INPUT && mode != OUTPUT ) return false; - if ( channels != 1 && channels != 2 ) { - errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; + if ( device >= rt_pa_info.dev.size() ) return false; + if ( firstChannel != 0 ) { + errorText_ = "PulseAudio does not support channel offset mapping."; return false; } - ss.channels = channels; - if ( firstChannel != 0 ) return false; + /* these may be NULL for default, but we've already got the names */ + const char *dev_input = NULL; + const char *dev_output = NULL; + if (!rt_pa_info.dev[device].source_name.empty()) + dev_input = rt_pa_info.dev[device].source_name.c_str(); + if (!rt_pa_info.dev[device].sink_name.empty()) + dev_output = rt_pa_info.dev[device].sink_name.c_str(); + + if (mode==INPUT && rt_pa_info.dev[device].info.inputChannels == 0) { + errorText_ = "PulseAudio device does not support input."; + return false; + } + if (mode==OUTPUT && rt_pa_info.dev[device].info.outputChannels == 0) { + errorText_ = "PulseAudio device does not support output."; + return false; + } + if (mode==DUPLEX && rt_pa_info.dev[device].info.duplexChannels == 0) { + /* Note: will always error, DUPLEX not yet supported */ + errorText_ = "PulseAudio device does not support duplex."; + return false; + } + + if (mode==INPUT && rt_pa_info.dev[device].info.inputChannels < channels) { + errorText_ = "PulseAudio: unsupported number of input channels."; + return false; + } + + if (mode==OUTPUT && rt_pa_info.dev[device].info.outputChannels < channels) { + errorText_ = "PulseAudio: unsupported number of output channels."; + return false; + } + + if (mode==DUPLEX && rt_pa_info.dev[device].info.duplexChannels < channels) { + /* Note: will always error, DUPLEX not yet supported */ + errorText_ = "PulseAudio: unsupported number of duplex channels."; + return false; + } + + ss.channels = channels; bool sr_found = false; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { @@ -8758,8 +9102,8 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, } } if ( !sr_found ) { - errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; - return false; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; } bool sf_found = 0; @@ -8783,7 +9127,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; - stream_.nBuffers = 1; + stream_.nBuffers = options ? options->numberOfBuffers : 1; stream_.doByteSwap[mode] = false; stream_.nUserChannels[mode] = channels; stream_.nDeviceChannels[mode] = channels + firstChannel; @@ -8796,6 +9140,8 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] ) + stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); @@ -8851,24 +9197,47 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, int error; if ( options && !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { - case INPUT: pa_buffer_attr buffer_attr; + case INPUT: buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; - pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); + pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, + dev_input, "Record", &ss, NULL, &buffer_attr, &error ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; - case OUTPUT: - pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + case OUTPUT: { + pa_buffer_attr * attr_ptr; + + if ( options && options->numberOfBuffers > 0 ) { + // pa_buffer_attr::fragsize is recording-only. + // Hopefully PortAudio won't access uninitialized fields. + buffer_attr.maxlength = bufferBytes * options->numberOfBuffers; + buffer_attr.minreq = -1; + buffer_attr.prebuf = -1; + buffer_attr.tlength = -1; + attr_ptr = &buffer_attr; + } else { + attr_ptr = nullptr; + } + + pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, + dev_output, "Playback", &ss, NULL, attr_ptr, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } break; + } + case DUPLEX: + /* Note: We could add DUPLEX by synchronizing multiple streams, + but it would mean moving from Simple API to Asynchronous API: + https://freedesktop.org/software/pulseaudio/doxygen/streams.html#sync_streams */ + errorText_ = "RtApiPulse::probeDeviceOpen: duplex not supported for PulseAudio."; + goto error; default: goto error; } @@ -10117,24 +10486,19 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info // data interleaving/deinterleaving. 24-bit integers are assumed to occupy // the lower three bytes of a 32-bit integer. - // Clear our device buffer when in/out duplex device channels are different - if ( outBuffer == stream_.deviceBuffer && stream_.mode == DUPLEX && - ( stream_.nDeviceChannels[0] < stream_.nDeviceChannels[1] ) ) + // Clear our duplex device output buffer if there are more device outputs than user outputs + if ( outBuffer == stream_.deviceBuffer && stream_.mode == DUPLEX && info.outJump > info.inJump ) memset( outBuffer, 0, stream_.bufferSize * info.outJump * formatBytes( info.outFormat ) ); int j; if (info.outFormat == RTAUDIO_FLOAT64) { - Float64 scale; Float64 *out = (Float64 *)outBuffer; if (info.inFormat == RTAUDIO_SINT8) { signed char *in = (signed char *)inBuffer; - scale = 1.0 / 127.5; for (unsigned int i=0; i -#if defined(__MACOSX_CORE__) - #if TARGET_OS_IPHONE +#if (TARGET_OS_IPHONE == 1) + #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos - #endif + + #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 @@ -57,11 +77,12 @@ // // **************************************************************** // -#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) +#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 { @@ -78,6 +99,7 @@ class MidiInCore: public MidiInApi std::string getPortName( unsigned int portNumber ); protected: + MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); void initialize( const std::string& clientName ); }; @@ -97,6 +119,7 @@ class MidiOutCore: public MidiOutApi void sendMessage( const unsigned char *message, size_t size ); protected: + MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); void initialize( const std::string& clientName ); }; @@ -231,6 +254,57 @@ class MidiOutWinMM: public MidiOutApi #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 @@ -285,6 +359,11 @@ RtMidi :: ~RtMidi() rtapi_ = 0; } +RtMidi::RtMidi(RtMidi&& other) noexcept { + rtapi_ = other.rtapi_; + other.rtapi_ = nullptr; +} + std::string RtMidi :: getVersion( void ) throw() { return std::string( RTMIDI_VERSION ); @@ -299,6 +378,7 @@ const char* rtmidi_api_names[][2] = { { "alsa" , "ALSA" }, { "jack" , "Jack" }, { "winmm" , "Windows MultiMedia" }, + { "web" , "Web MIDI API" }, { "dummy" , "Dummy" }, }; const unsigned int rtmidi_num_api_names = @@ -319,6 +399,9 @@ extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { #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 @@ -344,14 +427,14 @@ void RtMidi :: getCompiledApi( std::vector &apis ) throw() std::string RtMidi :: getApiName( RtMidi::Api api ) { - if (api < 0 || api >= RtMidi::NUM_APIS) + if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) return ""; return rtmidi_api_names[api][0]; } std::string RtMidi :: getApiDisplayName( RtMidi::Api api ) { - if (api < 0 || api >= RtMidi::NUM_APIS) + if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) return "Unknown"; return rtmidi_api_names[api][1]; } @@ -401,6 +484,10 @@ void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, un 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 ); @@ -469,6 +556,10 @@ void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) 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 ); @@ -634,6 +725,12 @@ double MidiInApi :: getMessage( std::vector *message ) 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 ) { @@ -716,10 +813,11 @@ MidiOutApi :: ~MidiOutApi( void ) // MIDI input. We convert the system specific time stamps to delta // time values. -// OS-X CoreMIDI header files. -#include -#include -#include +// 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. @@ -732,6 +830,20 @@ struct CoreMidiData { 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 @@ -899,24 +1011,37 @@ MidiInCore :: ~MidiInCore( void ) // Cleanup. CoreMidiData *data = static_cast (apiData_); - MIDIClientDispose( data->client ); 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; - 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; - } + MIDIClientRef client = getCoreMidiClientSingleton(clientName); // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; @@ -924,7 +1049,6 @@ void MidiInCore :: initialize( const std::string& clientName ) data->endpoint = 0; apiData_ = (void *) data; inputData_.apiData = (void *) data; - CFRelease( name ); } void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName ) @@ -960,7 +1084,6 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string &portNam CFRelease( portNameRef ); if ( result != noErr ) { - MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -970,7 +1093,6 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string &portNam MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); if ( endpoint == 0 ) { MIDIPortDispose( port ); - MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -980,7 +1102,6 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string &portNam result = MIDIPortConnectSource( port, endpoint, NULL ); if ( result != noErr ) { MIDIPortDispose( port ); - MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -1068,6 +1189,11 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) 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 ) @@ -1223,31 +1349,43 @@ MidiOutCore :: ~MidiOutCore( void ) // Cleanup. CoreMidiData *data = static_cast (apiData_); - MIDIClientDispose( data->client ); 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; - 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; - } + MIDIClientRef client = getCoreMidiClientSingleton(clientName); // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; data->client = client; data->endpoint = 0; apiData_ = (void *) data; - CFRelease( name ); } unsigned int MidiOutCore :: getPortCount() @@ -1310,7 +1448,6 @@ void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portNa OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); CFRelease( portNameRef ); if ( result != noErr ) { - MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -1320,7 +1457,6 @@ void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portNa MIDIEndpointRef destination = MIDIGetDestination( portNumber ); if ( destination == 0 ) { MIDIPortDispose( port ); - MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -1402,50 +1538,54 @@ void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) return; } - MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); - CoreMidiData *data = static_cast (apiData_); - OSStatus result; - if ( message[0] != 0xF0 && nBytes > 3 ) { errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; error( RtMidiError::WARNING, errorString_ ); return; } - Byte buffer[nBytes+(sizeof( MIDIPacketList ))]; + 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; - MIDIPacket *packet = MIDIPacketListInit( packetList ); ByteCount remainingBytes = nBytes; - while ( remainingBytes && packet ) { - ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket + 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_ ); + if ( !packet ) { + errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } - } - // 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_ ); + // 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_ ); + } } } } @@ -1487,6 +1627,7 @@ struct AlsaMidiData { 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; @@ -1517,7 +1658,6 @@ static void *alsaMidiHandler( void *ptr ) snd_seq_event_t *ev; int result; - apiData->bufferSize = 32; result = snd_midi_event_new( 0, &apiData->coder ); if ( result < 0 ) { data->doInput = false; @@ -1769,6 +1909,7 @@ void MidiInAlsa :: initialize( const std::string& clientName ) 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; @@ -2293,7 +2434,7 @@ void MidiOutAlsa :: openVirtualPort( const std::string &portName ) void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) { - int result; + long result; AlsaMidiData *data = static_cast (apiData_); unsigned int nBytes = static_cast (size); if ( nBytes > data->bufferSize ) { @@ -2313,25 +2454,38 @@ void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) } } - snd_seq_event_t ev; - snd_seq_ev_clear( &ev ); - snd_seq_ev_set_source( &ev, data->vport ); - snd_seq_ev_set_subs( &ev ); - snd_seq_ev_set_direct( &ev ); for ( unsigned int i=0; ibuffer[i] = message[i]; - result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); - if ( result < (int)nBytes ) { - errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; - error( RtMidiError::WARNING, errorString_ ); - return; - } - // Send the event. - result = snd_seq_event_output( data->seq, &ev ); - if ( result < 0 ) { - errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; - error( RtMidiError::WARNING, errorString_ ); - return; + 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 ); } @@ -2386,9 +2540,6 @@ static std::string ConvertToUTF8(const TCHAR *str) return u8str; } -#define RT_SYSEX_BUFFER_SIZE 1024 -#define RT_SYSEX_BUFFER_COUNT 4 - // A structure to hold variables related to the CoreMIDI API // implementation. struct WinMidiData { @@ -2396,7 +2547,7 @@ struct WinMidiData { HMIDIOUT outHandle; // Handle to Midi Output Device DWORD lastTime; MidiInApi::MidiMessage message; - LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; + std::vector sysexBuffer; CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo }; @@ -2576,10 +2727,11 @@ void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*port } // Allocate and init the sysex buffers. - for ( int i=0; isysexBuffer.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[ RT_SYSEX_BUFFER_SIZE ]; - data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; + 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; @@ -2630,7 +2782,7 @@ void MidiInWinMM :: closePort( void ) midiInReset( data->inHandle ); midiInStop( data->inHandle ); - for ( int i=0; isysexBuffer.size(); ++i ) { int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); delete [] data->sysexBuffer[i]->lpData; delete [] data->sysexBuffer[i]; @@ -2811,7 +2963,10 @@ void MidiOutWinMM :: closePort( void ) { if ( connected_ ) { WinMidiData *data = static_cast (apiData_); - midiOutReset( data->outHandle ); + // 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; @@ -2936,6 +3091,7 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) #include #include #include +#include #ifdef HAVE_SEMAPHORE #include #endif @@ -2945,8 +3101,8 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) struct JackMidiData { jack_client_t *client; jack_port_t *port; - jack_ringbuffer_t *buffSize; - jack_ringbuffer_t *buffMessage; + 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; @@ -3101,6 +3257,8 @@ void MidiInJack :: openPort( unsigned int portNumber, const std::string &portNam 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; } @@ -3123,6 +3281,8 @@ void MidiInJack :: openVirtualPort( const std::string &portName ) 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_ ); } } @@ -3226,11 +3386,15 @@ static int jackProcessOut( jack_nframes_t nframes, void *arg ) void *buff = jack_port_get_buffer( data->port, nframes ); jack_midi_clear_buffer( buff ); - while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { - jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof( space ) ); - midiData = jack_midi_event_reserve( buff, 0, space ); + 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) ); - jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) 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 @@ -3269,8 +3433,8 @@ void MidiOutJack :: connect() return; // Initialize output ringbuffers - data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); - data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); + 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 ) { @@ -3289,8 +3453,7 @@ MidiOutJack :: ~MidiOutJack() MidiOutJack::closePort(); // Cleanup - jack_ringbuffer_free( data->buffSize ); - jack_ringbuffer_free( data->buffMessage ); + jack_ringbuffer_free( data->buff ); if ( data->client ) { jack_client_close( data->client ); } @@ -3316,6 +3479,8 @@ void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portNa 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; } @@ -3338,6 +3503,8 @@ void MidiOutJack :: openVirtualPort( const std::string &portName ) 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_ ); } } @@ -3437,9 +3604,331 @@ 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->buffMessage, ( const char * ) message, nBytes ); - jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); + 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__