From caf7d2f2eb62b8e74fc5efe042612515a37d9543 Mon Sep 17 00:00:00 2001 From: Gary Scavone Date: Fri, 11 Apr 2014 09:29:42 -0400 Subject: [PATCH] Updated to new releases of RtAudio and RtMidi. --- include/RtAudio.h | 244 ++++- include/RtError.h | 60 -- include/RtMidi.h | 348 +++--- include/Stk.h | 2 +- projects/demo/demo.cpp | 6 +- src/Messager.cpp | 2 +- src/RtAudio.cpp | 2273 +++++++++++++++++++++++++++++++++------- src/RtMidi.cpp | 1738 ++++++++---------------------- src/RtWvIn.cpp | 2 +- src/RtWvOut.cpp | 2 +- 10 files changed, 2751 insertions(+), 1926 deletions(-) delete mode 100644 include/RtError.h diff --git a/include/RtAudio.h b/include/RtAudio.h index 31f1fa5..bc99bd9 100644 --- a/include/RtAudio.h +++ b/include/RtAudio.h @@ -5,12 +5,12 @@ RtAudio provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA, Jack, and OSS), Macintosh OS X (CoreAudio and Jack), and Windows - (DirectSound and ASIO) operating systems. + (DirectSound, ASIO and WASAPI) operating systems. RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes - Copyright (c) 2001-2012 Gary P. Scavone + Copyright (c) 2001-2014 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -42,14 +42,15 @@ \file RtAudio.h */ -// RtAudio: Version 4.0.11 - #ifndef __RTAUDIO_H #define __RTAUDIO_H +#define RTAUDIO_VERSION "4.1.0" + #include #include -#include "RtError.h" +#include +#include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. @@ -59,12 +60,10 @@ internal routines will automatically take care of any necessary byte-swapping between the host format and the soundcard. Thus, endian-ness is not a concern in the following format definitions. - Note that 24-bit data is expected to be encapsulated in a 32-bit - format. - \e RTAUDIO_SINT8: 8-bit signed integer. - \e RTAUDIO_SINT16: 16-bit signed integer. - - \e RTAUDIO_SINT24: Lower 3 bytes of 32-bit signed integer. + - \e RTAUDIO_SINT24: 24-bit signed integer. - \e RTAUDIO_SINT32: 32-bit signed integer. - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. @@ -72,7 +71,7 @@ typedef unsigned long RtAudioFormat; static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. -static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // Lower 3 bytes of 32-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. @@ -186,6 +185,63 @@ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, RtAudioStreamStatus status, void *userData ); +/************************************************************************/ +/*! \class RtAudioError + \brief Exception handling class for RtAudio. + + The RtAudioError class is quite simple but it does allow errors to be + "caught" by RtAudioError::Type. See the RtAudio documentation to know + which methods can throw an RtAudioError. +*/ +/************************************************************************/ + +class RtAudioError : public std::exception +{ + public: + //! Defined RtAudioError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {} + + //! The destructor. + virtual ~RtAudioError( void ) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType(void) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage(void) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char* what( void ) const throw() { return message_.c_str(); } + + protected: + std::string message_; + Type type_; +}; + +//! RtAudio error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + */ +typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); // **************************************************************** // // @@ -214,6 +270,7 @@ class RtAudio LINUX_OSS, /*!< The Linux Open Sound System API. */ UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ + WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_DS, /*!< The Microsoft Direct Sound API. */ RTAUDIO_DUMMY /*!< A compilable but non-functional API. */ @@ -316,6 +373,9 @@ class RtAudio : flags(0), numberOfBuffers(0), priority(0) {} }; + //! A static function to determine the current RtAudio version. + static std::string getVersion( void ) throw(); + //! A static function to determine the available compiled audio APIs. /*! The values returned in the std::vector can be compared against @@ -326,14 +386,14 @@ class RtAudio //! The class constructor. /*! - The constructor performs minor initialization tasks. No exceptions - can be thrown. + The constructor performs minor initialization tasks. An exception + can be thrown if no API support is compiled. If no API argument is specified and multiple API support has been compiled, the default order of use is JACK, ALSA, OSS (Linux systems) and ASIO, DS (Windows systems). */ - RtAudio( RtAudio::Api api=UNSPECIFIED ) throw(); + RtAudio( RtAudio::Api api=UNSPECIFIED ); //! The destructor. /*! @@ -357,7 +417,7 @@ class RtAudio /*! Any device integer between 0 and getDeviceCount() - 1 is valid. - If an invalid argument is provided, an RtError (type = INVALID_USE) + If an invalid argument is provided, an RtAudioError (type = INVALID_USE) will be thrown. If a device is busy or otherwise unavailable, the structure member "probed" will have a value of "false" and all other members are undefined. If the specified device is the @@ -388,9 +448,9 @@ class RtAudio //! A public function for opening a stream with the specified parameters. /*! - An RtError (type = SYSTEM_ERROR) is thrown if a stream cannot be + An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be opened with the specified parameters or an error occurs during - processing. An RtError (type = INVALID_USE) is thrown if any + processing. An RtAudioError (type = INVALID_USE) is thrown if any invalid device ID or channel number parameters are specified. \param outputParameters Specifies output stream parameters to use @@ -423,12 +483,14 @@ class RtAudio chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the 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. */ void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, - void *userData = NULL, RtAudio::StreamOptions *options = NULL ); + void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); //! A function that closes a stream and frees any associated stream memory. /*! @@ -439,8 +501,8 @@ class RtAudio //! A function that starts a stream. /*! - An RtError (type = SYSTEM_ERROR) is thrown if an error occurs - during processing. An RtError (type = INVALID_USE) is thrown if a + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already running. */ @@ -448,8 +510,8 @@ class RtAudio //! Stop a stream, allowing any samples remaining in the output queue to be played. /*! - An RtError (type = SYSTEM_ERROR) is thrown if an error occurs - during processing. An RtError (type = INVALID_USE) is thrown if a + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ @@ -457,8 +519,8 @@ class RtAudio //! Stop a stream, discarding any samples remaining in the input/output queue. /*! - An RtError (type = SYSTEM_ERROR) is thrown if an error occurs - during processing. An RtError (type = INVALID_USE) is thrown if a + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ @@ -472,7 +534,7 @@ class RtAudio //! Returns the number of elapsed seconds since the stream was started. /*! - If a stream is not open, an RtError (type = INVALID_USE) will be thrown. + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ double getStreamTime( void ); @@ -482,7 +544,7 @@ class RtAudio caused by internal buffering by the audio system and/or hardware. For duplex streams, the returned value will represent the sum of the input and output latencies. If a stream is not open, an - RtError (type = INVALID_USE) will be thrown. If the API does not + RtAudioError (type = INVALID_USE) will be thrown. If the API does not report latency, the return value will be zero. */ long getStreamLatency( void ); @@ -491,7 +553,7 @@ class RtAudio /*! On some systems, the sample rate used may be slightly different than that specified in the stream parameters. If a stream is not - open, an RtError (type = INVALID_USE) will be thrown. + open, an RtAudioError (type = INVALID_USE) will be thrown. */ unsigned int getStreamSampleRate( void ); @@ -505,11 +567,11 @@ class RtAudio }; // Operating system dependent thread functionality. -#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) #include #include - typedef unsigned long ThreadHandle; + typedef ULONG_PTR ThreadHandle; typedef CRITICAL_SECTION StreamMutex; #elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) @@ -535,12 +597,15 @@ struct CallbackInfo { 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), apiInfo(0), isRunning(false) {} + :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false) {} }; // **************************************************************** // @@ -557,6 +622,36 @@ struct CallbackInfo { // // **************************************************************** // +#pragma pack(push, 1) +class S24 { + + protected: + unsigned char c3[3]; + + public: + S24() {} + + S24& operator = ( const int& i ) { + c3[0] = (i & 0x000000ff); + c3[1] = (i & 0x0000ff00) >> 8; + c3[2] = (i & 0x00ff0000) >> 16; + return *this; + } + + S24( const S24& v ) { *this = v; } + S24( const double& d ) { *this = (int) d; } + S24( const float& f ) { *this = (int) f; } + S24( const signed short& s ) { *this = (int) s; } + S24( const char& c ) { *this = (int) c; } + + int asInt() { + int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); + if (i & 0x800000) i |= ~0xffffff; + return i; + } +}; +#pragma pack(pop) + #if defined( HAVE_GETTIMEOFDAY ) #include #endif @@ -578,7 +673,8 @@ public: RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, - void *userData, RtAudio::StreamOptions *options ); + void *userData, RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ); virtual void closeStream( void ); virtual void startStream( void ) = 0; virtual void stopStream( void ) = 0; @@ -586,9 +682,9 @@ public: long getStreamLatency( void ); unsigned int getStreamSampleRate( void ); virtual double getStreamTime( void ); - bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; }; - bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; }; - void showWarnings( bool value ) { showWarnings_ = value; }; + bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } + bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } + void showWarnings( bool value ) { showWarnings_ = value; } protected: @@ -655,6 +751,7 @@ protected: :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } }; + typedef S24 Int24; typedef signed short Int16; typedef signed int Int32; typedef float Float32; @@ -664,6 +761,7 @@ protected: std::string errorText_; bool showWarnings_; RtApiStream stream_; + bool firstErrorOccurred_; /*! Protected, api-specific method that attempts to open a device @@ -684,13 +782,13 @@ protected: void clearStreamInfo(); /*! - Protected common method that throws an RtError (type = + Protected common method that throws an RtAudioError (type = INVALID_USE) if a stream is not open. */ void verifyStream( void ); //! Protected common error method to allow global control over error handling. - void error( RtError::Type type ); + void error( RtAudioError::Type type ); /*! Protected method used to perform format, channel number, and/or interleaving @@ -726,7 +824,7 @@ inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } inline bool RtAudio :: isStreamOpen( void ) const throw() { return rtapi_->isStreamOpen(); } inline bool RtAudio :: isStreamRunning( void ) const throw() { return rtapi_->isStreamRunning(); } inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } -inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); }; +inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: showWarnings( bool value ) throw() { rtapi_->showWarnings( value ); } @@ -742,7 +840,7 @@ public: RtApiCore(); ~RtApiCore(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; }; + RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); unsigned int getDefaultOutputDevice( void ); @@ -780,7 +878,7 @@ public: RtApiJack(); ~RtApiJack(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; }; + RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); @@ -813,7 +911,7 @@ public: RtApiAsio(); ~RtApiAsio(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; }; + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); @@ -849,7 +947,7 @@ public: RtApiDs(); ~RtApiDs(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; }; + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } unsigned int getDeviceCount( void ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); @@ -871,6 +969,7 @@ public: bool coInitialized_; bool buffersRolling; long duplexPrerollBytes; + std::vector dsDevices; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, @@ -879,6 +978,43 @@ public: #endif +#if defined(__WINDOWS_WASAPI__) + +struct IMMDeviceEnumerator; + +class RtApiWasapi : public RtApi +{ +public: + RtApiWasapi(); + ~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 ); + +private: + bool coInitialized_; + IMMDeviceEnumerator* deviceEnumerator_; + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ); + + static DWORD WINAPI runWasapiThread( void* wasapiPtr ); + static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); + static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); + void wasapiThread(); +}; + +#endif + #if defined(__LINUX_ALSA__) class RtApiAlsa: public RtApi @@ -887,7 +1023,7 @@ public: RtApiAlsa(); ~RtApiAlsa(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; }; + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); @@ -919,7 +1055,7 @@ class RtApiPulse: public RtApi { public: ~RtApiPulse(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; }; + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); @@ -953,7 +1089,7 @@ public: RtApiOss(); ~RtApiOss(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; }; + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); @@ -983,21 +1119,21 @@ class RtApiDummy: public RtApi { public: - RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtError::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 ) {}; + 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 ) {} 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; }; + 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; } }; #endif diff --git a/include/RtError.h b/include/RtError.h deleted file mode 100644 index a64f434..0000000 --- a/include/RtError.h +++ /dev/null @@ -1,60 +0,0 @@ -/************************************************************************/ -/*! \class RtError - \brief Exception handling class for RtAudio & RtMidi. - - The RtError class is quite simple but it does allow errors to be - "caught" by RtError::Type. See the RtAudio and RtMidi - documentation to know which methods can throw an RtError. - -*/ -/************************************************************************/ - -#ifndef RTERROR_H -#define RTERROR_H - -#include -#include -#include - -class RtError : public std::exception -{ - public: - //! Defined RtError types. - enum Type { - WARNING, /*!< A non-critical error. */ - DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ - UNSPECIFIED, /*!< The default, unspecified error type. */ - NO_DEVICES_FOUND, /*!< No devices found on system. */ - INVALID_DEVICE, /*!< An invalid device ID was specified. */ - MEMORY_ERROR, /*!< An error occured during memory allocation. */ - INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ - INVALID_USE, /*!< The function was called incorrectly. */ - DRIVER_ERROR, /*!< A system driver error occured. */ - SYSTEM_ERROR, /*!< A system error occured. */ - THREAD_ERROR /*!< A thread error occured. */ - }; - - //! The constructor. - RtError( const std::string& message, Type type = RtError::UNSPECIFIED ) throw() : message_(message), type_(type) {} - - //! The destructor. - virtual ~RtError( void ) throw() {} - - //! Prints thrown error message to stderr. - virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } - - //! Returns the thrown error message type. - virtual const Type& getType(void) const throw() { return type_; } - - //! Returns the thrown error message string. - virtual const std::string& getMessage(void) const throw() { return message_; } - - //! Returns the thrown error message as a c-style string. - virtual const char* what( void ) const throw() { return message_.c_str(); } - - protected: - std::string message_; - Type type_; -}; - -#endif diff --git a/include/RtMidi.h b/include/RtMidi.h index 127a01c..9a87888 100644 --- a/include/RtMidi.h +++ b/include/RtMidi.h @@ -8,7 +8,7 @@ RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes - Copyright (c) 2003-2012 Gary P. Scavone + Copyright (c) 2003-2014 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -40,15 +40,79 @@ \file RtMidi.h */ -// RtMidi: Version 2.0.1 - #ifndef RTMIDI_H #define RTMIDI_H -#include "RtError.h" +#define RTMIDI_VERSION "2.1.0" + +#include +#include #include #include +/************************************************************************/ +/*! \class RtMidiError + \brief Exception handling class for RtMidi. + + The RtMidiError class is quite simple but it does allow errors to be + "caught" by RtMidiError::Type. See the RtMidi documentation to know + which methods can throw an RtMidiError. +*/ +/************************************************************************/ + +class RtMidiError : public std::exception +{ + public: + //! Defined RtMidiError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {} + + //! The destructor. + virtual ~RtMidiError( void ) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType(void) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage(void) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char* what( void ) const throw() { return message_.c_str(); } + + protected: + std::string message_; + Type type_; +}; + +//! RtMidi error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + + Note that class behaviour is undefined after a critical error (not + a warning) is reported. + */ +typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText ); + +class MidiApi; + class RtMidi { public: @@ -58,12 +122,14 @@ class RtMidi UNSPECIFIED, /*!< Search for a working compiled API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ - UNIX_JACK, /*!< The Jack Low-Latency MIDI Server API. */ + UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ - WINDOWS_KS, /*!< The Microsoft Kernel Streaming MIDI API. */ RTMIDI_DUMMY /*!< A compilable but non-functional API. */ }; + //! A static function to determine the current RtMidi version. + static std::string getVersion( void ) throw(); + //! A static function to determine the available compiled MIDI APIs. /*! The values returned in the std::vector can be compared against @@ -87,13 +153,22 @@ class RtMidi //! Pure virtual closePort() function. virtual void closePort( void ) = 0; - //! A basic error reporting function for RtMidi classes. - static void error( RtError::Type type, std::string errorString ); + //! Returns true if a port is open and false if not. + virtual bool isPortOpen( void ) const = 0; + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL ) = 0; protected: - RtMidi() {}; - virtual ~RtMidi() {}; + RtMidi(); + virtual ~RtMidi(); + + MidiApi *rtapi_; }; /**********************************************************************/ @@ -106,11 +181,11 @@ class RtMidi retrieval using the getMessage() function or immediately passed to a user-specified callback function. Create multiple instances of this class to connect to more than one MIDI device at the same - time. With the OS-X and Linux ALSA MIDI APIs, it is also possible - to open a virtual input port to which other MIDI software clients - can connect. + time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also + possible to open a virtual input port to which other MIDI software + clients can connect. - by Gary P. Scavone, 2003-2012. + by Gary P. Scavone, 2003-2014. */ /**********************************************************************/ @@ -128,9 +203,6 @@ class RtMidi // // **************************************************************** // -class MidiInApi; -class MidiOutApi; - class RtMidiIn : public RtMidi { public: @@ -147,8 +219,14 @@ class RtMidiIn : public RtMidi incoming messages will be ignored. If no API argument is specified and multiple API support has been - compiled, the default order of use is JACK, ALSA (Linux) and CORE, - Jack (OS-X). + compiled, the default order of use is ALSA, JACK (Linux) and CORE, + JACK (OS-X). + + \param api An optional API id can be specified. + \param clientName An optional client name can be specified. This + will be used to group the ports that are created + by the application. + \param queueSizeLimit An optional size of the MIDI input queue can be specified. */ RtMidiIn( RtMidi::Api api=UNSPECIFIED, const std::string clientName = std::string( "RtMidi Input Client"), @@ -160,19 +238,23 @@ class RtMidiIn : public RtMidi //! Returns the MIDI API specifier for the current instance of RtMidiIn. RtMidi::Api getCurrentApi( void ) throw(); - //! Open a MIDI input connection. + //! Open a MIDI input connection given by enumeration number. /*! - An optional port number greater than 0 can be specified. - Otherwise, the default or first port found is opened. + \param portNumber An optional port number greater than 0 can be specified. + Otherwise, the default or first port found is opened. + \param portName An optional name for the application port that is used to connect to portId can be specified. */ void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) ); - //! Create a virtual input port, with optional name, to allow software connections (OS X and ALSA only). + //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*! This function creates a virtual MIDI input port to which other software applications can connect. This type of functionality - is currently only supported by the Macintosh OS-X and Linux ALSA - APIs (the function does nothing for the other APIs). + is currently only supported by the Macintosh OS-X, any JACK, + and Linux ALSA APIs (the function returns an error for the other APIs). + + \param portName An optional name for the application port that is + used to connect to portId can be specified. */ void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) ); @@ -182,6 +264,10 @@ class RtMidiIn : public RtMidi message is received. While not absolutely necessary, it is best to set the callback function before opening a MIDI port to avoid leaving some messages in the queue. + + \param callback A callback function must be given. + \param userData Optionally, a pointer to additional data can be + passed to the callback function whenever it is called. */ void setCallback( RtMidiCallback callback, void *userData = 0 ); @@ -195,18 +281,25 @@ class RtMidiIn : public RtMidi //! Close an open MIDI connection (if one exists). void closePort( void ); + //! Returns true if a port is open and false if not. + virtual bool isPortOpen() const; + //! Return the number of available MIDI input ports. + /*! + \return This function returns the number of MIDI ports of the selected API. + */ unsigned int getPortCount(); //! Return a string identifier for the specified MIDI input port number. /*! - An empty string is returned if an invalid port specifier is provided. + \return The name of the port with the given Id is returned. + \retval An empty string is returned if an invalid port specifier is provided. */ std::string getPortName( unsigned int portNumber = 0 ); //! Specify whether certain MIDI message types should be queued or ignored during input. /*! - o By default, MIDI timing and active sensing messages are ignored + By default, MIDI timing and active sensing messages are ignored during message input because of their relative high data rates. MIDI sysex messages are ignored by default as well. Variable values of "true" imply that the respective message type will be @@ -224,9 +317,15 @@ class RtMidiIn : public RtMidi */ double getMessage( std::vector *message ); + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL ); + protected: void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ); - MidiInApi *rtapi_; }; @@ -239,10 +338,10 @@ class RtMidiIn : public RtMidi connect to one such port, and to send MIDI bytes immediately over the connection. Create multiple instances of this class to connect to more than one MIDI device at the same time. With the - OS-X and Linux ALSA MIDI APIs, it is also possible to open a + OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a virtual port to which other MIDI software clients can connect. - by Gary P. Scavone, 2003-2012. + by Gary P. Scavone, 2003-2014. */ /**********************************************************************/ @@ -255,8 +354,8 @@ class RtMidiOut : public RtMidi An exception will be thrown if a MIDI system initialization error occurs. If no API argument is specified and multiple API support has been - compiled, the default order of use is JACK, ALSA (Linux) and CORE, - Jack (OS-X). + compiled, the default order of use is ALSA, JACK (Linux) and CORE, + JACK (OS-X). */ RtMidiOut( RtMidi::Api api=UNSPECIFIED, const std::string clientName = std::string( "RtMidi Output Client") ); @@ -279,14 +378,17 @@ class RtMidiOut : public RtMidi //! Close an open MIDI connection (if one exists). void closePort( void ); - //! Create a virtual output port, with optional name, to allow software connections (OS X and ALSA only). + //! Returns true if a port is open and false if not. + virtual bool isPortOpen() const; + + //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*! This function creates a virtual MIDI output port to which other software applications can connect. This type of functionality - is currently only supported by the Macintosh OS-X and Linux ALSA - APIs (the function does nothing with the other APIs). An - exception is thrown if an error occurs while attempting to create - the virtual port. + is currently only supported by the Macintosh OS-X, Linux ALSA + and JACK APIs (the function does nothing with the other APIs). + An exception is thrown if an error occurs while attempting to + create the virtual port. */ void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) ); @@ -306,9 +408,15 @@ class RtMidiOut : public RtMidi */ void sendMessage( std::vector *message ); + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL ); + protected: void openMidiApi( RtMidi::Api api, const std::string clientName ); - MidiOutApi *rtapi_; }; @@ -325,20 +433,43 @@ class RtMidiOut : public RtMidi // // **************************************************************** // -class MidiInApi +class MidiApi +{ + public: + + MidiApi(); + virtual ~MidiApi(); + virtual RtMidi::Api getCurrentApi( void ) = 0; + virtual void openPort( unsigned int portNumber, const std::string portName ) = 0; + virtual void openVirtualPort( const std::string portName ) = 0; + virtual void closePort( void ) = 0; + + virtual unsigned int getPortCount( void ) = 0; + virtual std::string getPortName( unsigned int portNumber ) = 0; + + inline bool isPortOpen() const { return connected_; } + void setErrorCallback( RtMidiErrorCallback errorCallback ); + + //! A basic error reporting function for RtMidi classes. + void error( RtMidiError::Type type, std::string errorString ); + +protected: + virtual void initialize( const std::string& clientName ) = 0; + + void *apiData_; + bool connected_; + std::string errorString_; + RtMidiErrorCallback errorCallback_; +}; + +class MidiInApi : public MidiApi { public: MidiInApi( unsigned int queueSizeLimit ); virtual ~MidiInApi( void ); - virtual RtMidi::Api getCurrentApi( void ) = 0; - virtual void openPort( unsigned int portNumber, const std::string portName ) = 0; - virtual void openVirtualPort( const std::string portName ) = 0; - virtual void closePort( void ) = 0; void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); void cancelCallback( void ); - virtual unsigned int getPortCount( void ) = 0; - virtual std::string getPortName( unsigned int portNumber ) = 0; virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); double getMessage( std::vector *message ); @@ -375,7 +506,7 @@ class MidiInApi bool firstMessage; void *apiData; bool usingCallback; - void *userCallback; + RtMidiIn::RtMidiCallback userCallback; void *userData; bool continueSysex; @@ -387,34 +518,16 @@ class MidiInApi }; protected: - virtual void initialize( const std::string& clientName ) = 0; RtMidiInData inputData_; - - void *apiData_; - bool connected_; - std::string errorString_; }; -class MidiOutApi +class MidiOutApi : public MidiApi { public: MidiOutApi( void ); virtual ~MidiOutApi( void ); - virtual RtMidi::Api getCurrentApi( void ) = 0; - virtual void openPort( unsigned int portNumber, const std::string portName ) = 0; - virtual void openVirtualPort( const std::string portName ) = 0; - virtual void closePort( void ) = 0; - virtual unsigned int getPortCount( void ) = 0; - virtual std::string getPortName( unsigned int portNumber ) = 0; virtual void sendMessage( std::vector *message ) = 0; - - protected: - virtual void initialize( const std::string& clientName ) = 0; - - void *apiData_; - bool connected_; - std::string errorString_; }; // **************************************************************** // @@ -424,23 +537,27 @@ class MidiOutApi // **************************************************************** // inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } -inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { return rtapi_->openPort( portNumber, portName ); } -inline void RtMidiIn :: openVirtualPort( const std::string portName ) { return rtapi_->openVirtualPort( portName ); } -inline void RtMidiIn :: closePort( void ) { return rtapi_->closePort(); } -inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { return rtapi_->setCallback( callback, userData ); } -inline void RtMidiIn :: cancelCallback( void ) { return rtapi_->cancelCallback(); } +inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); } +inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); } +inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } +inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } +inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); } +inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); } inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } -inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { return rtapi_->ignoreTypes( midiSysex, midiTime, midiSense ); } -inline double RtMidiIn :: getMessage( std::vector *message ) { return rtapi_->getMessage( message ); } +inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } +inline double RtMidiIn :: getMessage( std::vector *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); } +inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback ) { rtapi_->setErrorCallback(errorCallback); } inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } -inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { return rtapi_->openPort( portNumber, portName ); } -inline void RtMidiOut :: openVirtualPort( const std::string portName ) { return rtapi_->openVirtualPort( portName ); } -inline void RtMidiOut :: closePort( void ) { return rtapi_->closePort(); } +inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); } +inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); } +inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } +inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } -inline void RtMidiOut :: sendMessage( std::vector *message ) { return rtapi_->sendMessage( message ); } +inline void RtMidiOut :: sendMessage( std::vector *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); } +inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback ) { rtapi_->setErrorCallback(errorCallback); } // **************************************************************** // // @@ -448,7 +565,7 @@ inline void RtMidiOut :: sendMessage( std::vector *message ) { re // // **************************************************************** // -#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(__WINDOWS_KS__) +#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) #define __RTMIDI_DUMMY__ #endif @@ -504,6 +621,9 @@ class MidiInJack: public MidiInApi std::string getPortName( unsigned int portNumber ); protected: + std::string clientName; + + void connect( void ); void initialize( const std::string& clientName ); }; @@ -521,6 +641,9 @@ class MidiOutJack: public MidiOutApi void sendMessage( std::vector *message ); protected: + std::string clientName; + + void connect( void ); void initialize( const std::string& clientName ); }; @@ -600,74 +723,37 @@ class MidiOutWinMM: public MidiOutApi #endif -#if defined(__WINDOWS_KS__) - -class MidiInWinKS: public MidiInApi -{ - public: - MidiInWinKS( const std::string clientName, unsigned int queueSizeLimit ); - ~MidiInWinKS( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_KS; }; - void openPort( unsigned int portNumber, const std::string portName ); - void openVirtualPort( const std::string portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - - protected: - void initialize( const std::string& clientName ); -}; - -class MidiOutWinKS: public MidiOutApi -{ - public: - MidiOutWinKS( const std::string clientName ); - ~MidiOutWinKS( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_KS; }; - void openPort( unsigned int portNumber, const std::string portName ); - void openVirtualPort( const std::string portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - void sendMessage( std::vector *message ); - - protected: - void initialize( const std::string& clientName ); -}; - -#endif - #if defined(__RTMIDI_DUMMY__) class MidiInDummy: public MidiInApi { public: - MidiInDummy( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; RtMidi::error( RtError::WARNING, errorString_ ); }; - RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }; - void openPort( unsigned int portNumber, const std::string portName ) {}; - void openVirtualPort( const std::string portName ) {}; - void closePort( void ) {}; - unsigned int getPortCount( void ) { return 0; }; - std::string getPortName( unsigned int portNumber ) { return ""; }; + MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } + RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {} + void openVirtualPort( const std::string /*portName*/ ) {} + void closePort( void ) {} + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int portNumber ) { return ""; } protected: - void initialize( const std::string& clientName ) {}; + void initialize( const std::string& /*clientName*/ ) {} }; class MidiOutDummy: public MidiOutApi { public: - MidiOutDummy( const std::string clientName ) { errorString_ = "MidiOutDummy: This class provides no functionality."; RtMidi::error( RtError::WARNING, errorString_ ); }; - RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }; - void openPort( unsigned int portNumber, const std::string portName ) {}; - void openVirtualPort( const std::string portName ) {}; - void closePort( void ) {}; - unsigned int getPortCount( void ) { return 0; }; - std::string getPortName( unsigned int portNumber ) { return ""; }; - void sendMessage( std::vector *message ) {}; + MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } + RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {} + void openVirtualPort( const std::string /*portName*/ ) {} + void closePort( void ) {} + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } + void sendMessage( std::vector * /*message*/ ) {} protected: - void initialize( const std::string& clientName ) {}; + void initialize( const std::string& /*clientName*/ ) {} }; #endif diff --git a/include/Stk.h b/include/Stk.h index d5ecf61..44b3aa6 100644 --- a/include/Stk.h +++ b/include/Stk.h @@ -11,7 +11,7 @@ \brief The STK namespace. Most Stk classes are defined within the STK namespace. Exceptions - to this include the classes RtAudio, RtMidi, and RtError. + to this include the classes RtAudio and RtMidi. */ namespace stk { diff --git a/projects/demo/demo.cpp b/projects/demo/demo.cpp index c354364..e2f6e2a 100644 --- a/projects/demo/demo.cpp +++ b/projects/demo/demo.cpp @@ -262,7 +262,7 @@ int main( int argc, char *argv[] ) try { dac.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data ); } - catch ( RtError& error ) { + catch ( RtAudioError& error ) { error.printMessage(); goto cleanup; } @@ -282,7 +282,7 @@ int main( int argc, char *argv[] ) try { dac.startStream(); } - catch ( RtError &error ) { + catch ( RtAudioError &error ) { error.printMessage(); goto cleanup; } @@ -307,7 +307,7 @@ int main( int argc, char *argv[] ) try { dac.closeStream(); } - catch ( RtError& error ) { + catch ( RtAudioError& error ) { error.printMessage(); } } diff --git a/src/Messager.cpp b/src/Messager.cpp index 0ce312d..929f7e7 100644 --- a/src/Messager.cpp +++ b/src/Messager.cpp @@ -255,7 +255,7 @@ bool Messager :: startMidiInput( int port ) if ( port == -1 ) data_.midi->openVirtualPort(); else data_.midi->openPort( (unsigned int)port ); } - catch ( RtError &error ) { + catch ( RtMidiError &error ) { oStream_ << "Messager::startMidiInput: error creating RtMidiIn instance (" << error.getMessage() << ")."; handleError( StkError::WARNING ); return false; diff --git a/src/RtAudio.cpp b/src/RtAudio.cpp index 823faaf..eb3eadc 100644 --- a/src/RtAudio.cpp +++ b/src/RtAudio.cpp @@ -5,12 +5,12 @@ RtAudio provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA, Jack, and OSS), Macintosh OS X (CoreAudio and Jack), and Windows - (DirectSound and ASIO) operating systems. + (DirectSound, ASIO and WASAPI) operating systems. RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes - Copyright (c) 2001-2012 Gary P. Scavone + Copyright (c) 2001-2014 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -38,7 +38,7 @@ */ /************************************************************************/ -// RtAudio: Version 4.0.11 +// RtAudio: Version 4.1.0 #include "RtAudio.h" #include @@ -53,7 +53,7 @@ const unsigned int RtApi::SAMPLE_RATES[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; -#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) @@ -75,6 +75,11 @@ const unsigned int RtApi::SAMPLE_RATES[] = { // // *************************************************** // +std::string RtAudio :: getVersion( void ) throw() +{ + return RTAUDIO_VERSION; +} + void RtAudio :: getCompiledApi( std::vector &apis ) throw() { apis.clear(); @@ -96,6 +101,9 @@ void RtAudio :: getCompiledApi( std::vector &apis ) throw() #if defined(__WINDOWS_ASIO__) apis.push_back( WINDOWS_ASIO ); #endif +#if defined(__WINDOWS_WASAPI__) + apis.push_back( WINDOWS_WASAPI ); +#endif #if defined(__WINDOWS_DS__) apis.push_back( WINDOWS_DS ); #endif @@ -133,6 +141,10 @@ void RtAudio :: openRtApi( RtAudio::Api api ) if ( api == WINDOWS_ASIO ) rtapi_ = new RtApiAsio(); #endif +#if defined(__WINDOWS_WASAPI__) + if ( api == WINDOWS_WASAPI ) + rtapi_ = new RtApiWasapi(); +#endif #if defined(__WINDOWS_DS__) if ( api == WINDOWS_DS ) rtapi_ = new RtApiDs(); @@ -147,7 +159,7 @@ void RtAudio :: openRtApi( RtAudio::Api api ) #endif } -RtAudio :: RtAudio( RtAudio::Api api ) throw() +RtAudio :: RtAudio( RtAudio::Api api ) { rtapi_ = 0; @@ -175,13 +187,15 @@ RtAudio :: RtAudio( RtAudio::Api api ) throw() // 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 print out an error message. - std::cerr << "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + // case something weird happens, we'll thow an error. + std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } RtAudio :: ~RtAudio() throw() { - delete rtapi_; + if ( rtapi_ ) + delete rtapi_; } void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, @@ -189,11 +203,12 @@ void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, - RtAudio::StreamOptions *options ) + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, - userData, options ); + userData, options, errorCallback ); } // *************************************************** // @@ -212,6 +227,7 @@ RtApi :: RtApi() stream_.userBuffer[1] = 0; MUTEX_INITIALIZE( &stream_.mutex ); showWarnings_ = true; + firstErrorOccurred_ = false; } RtApi :: ~RtApi() @@ -224,31 +240,40 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, - RtAudio::StreamOptions *options ) + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } + // Clear stream information potentially left from a previously open stream. + clearStreamInfo(); + if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } unsigned int nDevices = getDeviceCount(); @@ -257,7 +282,8 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, oChannels = oParams->nChannels; if ( oParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: output device parameter value is invalid."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } } @@ -266,18 +292,21 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, iChannels = iParams->nChannels; if ( iParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: input device parameter value is invalid."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } } - clearStreamInfo(); bool result; if ( oChannels > 0 ) { result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); - if ( result == false ) error( RtError::SYSTEM_ERROR ); + if ( result == false ) { + error( RtAudioError::SYSTEM_ERROR ); + return; + } } if ( iChannels > 0 ) { @@ -286,12 +315,14 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, sampleRate, format, bufferFrames, options ); if ( result == false ) { if ( oChannels > 0 ) closeStream(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; + stream_.callbackInfo.errorCallback = (void *) errorCallback; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; @@ -315,10 +346,10 @@ void RtApi :: closeStream( void ) return; } -bool RtApi :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, - unsigned int firstChannel, unsigned int sampleRate, - RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ) +bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) { // MUST be implemented in subclasses! return FAILURE; @@ -423,8 +454,6 @@ struct CoreHandle { :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; -ThreadHandle threadId; - RtApiCore:: RtApiCore() { #if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) @@ -439,7 +468,7 @@ RtApiCore:: RtApiCore() OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } #endif } @@ -460,7 +489,7 @@ unsigned int RtApiCore :: getDeviceCount( void ) OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -478,7 +507,7 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -488,7 +517,7 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -496,7 +525,7 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) if ( id == deviceList[i] ) return i; errorText_ = "RtApiCore::getDefaultInputDevice: No default device found!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -511,7 +540,7 @@ unsigned int RtApiCore :: getDefaultOutputDevice( void ) OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -521,7 +550,7 @@ unsigned int RtApiCore :: getDefaultOutputDevice( void ) result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device IDs."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -529,7 +558,7 @@ unsigned int RtApiCore :: getDefaultOutputDevice( void ) if ( id == deviceList[i] ) return i; errorText_ = "RtApiCore::getDefaultOutputDevice: No default device found!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -542,12 +571,14 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiCore::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } if ( device >= nDevices ) { errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } AudioDeviceID deviceList[ nDevices ]; @@ -559,7 +590,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -574,14 +605,18 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); int length = CFStringGetLength(cfname); char *mname = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); +#else CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); +#endif info.name.append( (const char *)mname, strlen(mname) ); info.name.append( ": " ); CFRelease( cfname ); @@ -592,14 +627,18 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); length = CFStringGetLength(cfname); char *name = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); +#else CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); +#endif info.name.append( (const char *)name, strlen(name) ); CFRelease( cfname ); free(name); @@ -614,7 +653,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -622,7 +661,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -631,7 +670,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -647,7 +686,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -655,7 +694,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -664,7 +703,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -689,7 +728,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != kAudioHardwareNoError || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -699,26 +738,45 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != kAudioHardwareNoError ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } - Float64 minimumRate = 100000000.0, maximumRate = 0.0; + // The sample rate reporting mechanism is a bit of a mystery. It + // seems that it can either return individual rates or a range of + // rates. I assume that if the min / max range values are the same, + // then that represents a single supported rate and if the min / max + // range values are different, the device supports an arbitrary + // range of values (though there might be multiple ranges, so we'll + // use the most conservative range). + Float64 minimumRate = 1.0, maximumRate = 10000000000.0; + bool haveValueRange = false; + info.sampleRates.clear(); for ( UInt32 i=0; i maximumRate ) maximumRate = rangeList[i].mMaximum; + if ( rangeList[i].mMinimum == rangeList[i].mMaximum ) + info.sampleRates.push_back( (unsigned int) rangeList[i].mMinimum ); + else { + haveValueRange = true; + if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; + if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; + } } - info.sampleRates.clear(); - for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) - info.sampleRates.push_back( SAMPLE_RATES[k] ); + if ( haveValueRange ) { + for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) + info.sampleRates.push_back( SAMPLE_RATES[k] ); + } } + // Sort and remove any redundant values + std::sort( info.sampleRates.begin(), info.sampleRates.end() ); + info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); + if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -736,13 +794,13 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) return info; } -OSStatus callbackHandler( AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* infoPointer ) +static OSStatus callbackHandler( AudioDeviceID inDevice, + const AudioTimeStamp* /*inNow*/, + const AudioBufferList* inInputData, + const AudioTimeStamp* /*inInputTime*/, + AudioBufferList* outOutputData, + const AudioTimeStamp* /*inOutputTime*/, + void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; @@ -753,10 +811,10 @@ OSStatus callbackHandler( AudioDeviceID inDevice, return kAudioHardwareNoError; } -OSStatus xrunListener( AudioObjectID inDevice, - UInt32 nAddresses, - const AudioObjectPropertyAddress properties[], - void* handlePointer ) +static OSStatus xrunListener( AudioObjectID /*inDevice*/, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* handlePointer ) { CoreHandle *handle = (CoreHandle *) handlePointer; for ( UInt32 i=0; iobject; @@ -1475,7 +1534,7 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } @@ -1484,6 +1543,7 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) @@ -1821,8 +1881,7 @@ struct JackHandle { :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; -ThreadHandle threadId; -void jackSilentError( const char * ) {}; +static void jackSilentError( const char * ) {}; RtApiJack :: RtApiJack() { @@ -1881,7 +1940,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); if ( client == 0 ) { errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -1910,7 +1969,8 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) if ( device >= nDevices ) { jack_client_close( client ); errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } // Get the current jack server sample rate. @@ -1939,7 +1999,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) if ( info.outputChannels == 0 && info.inputChannels == 0 ) { jack_client_close(client); errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -1961,7 +2021,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) return info; } -int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; @@ -1975,7 +2035,7 @@ int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before // the jack_deactivate() function (in closeStream()) will return. -extern "C" void *jackCloseStream( void *ptr ) +static void *jackCloseStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; @@ -1984,7 +2044,7 @@ extern "C" void *jackCloseStream( void *ptr ) pthread_exit( NULL ); } -void jackShutdown( void *infoPointer ) +static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; @@ -1996,11 +2056,12 @@ void jackShutdown( void *infoPointer ) // other problem occurred and we should close the stream. if ( object->isStreamRunning() == false ) return; + ThreadHandle threadId; pthread_create( &threadId, NULL, jackCloseStream, info ); std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; } -int jackXrun( void *infoPointer ) +static int jackXrun( void *infoPointer ) { JackHandle *handle = (JackHandle *) infoPointer; @@ -2028,7 +2089,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne client = jack_client_open( "RtApiJack", jackoptions, status ); if ( client == 0 ) { errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } } @@ -2094,8 +2155,17 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // Get the latency of the JACK port. ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); - if ( ports[ firstChannel ] ) - stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + if ( ports[ firstChannel ] ) { + // Added by Ge Wang + jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); + // the range (usually the min and max are equal) + jack_latency_range_t latrange; latrange.min = latrange.max = 0; + // get the latency range + jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); + // be optimistic, use the min! + stream_.latency[mode] = latrange.min; + //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + } free( ports ); // The jack server always uses 32-bit floating-point data. @@ -2256,7 +2326,7 @@ void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -2298,7 +2368,7 @@ void RtApiJack :: startStream( void ) verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiJack::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -2364,7 +2434,7 @@ void RtApiJack :: startStream( void ) unlock: if ( result == 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiJack :: stopStream( void ) @@ -2372,7 +2442,7 @@ void RtApiJack :: stopStream( void ) verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -2394,7 +2464,7 @@ void RtApiJack :: abortStream( void ) verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -2409,7 +2479,7 @@ void RtApiJack :: abortStream( void ) // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the jack_deactivate() // function will return. -extern "C" void *jackStopStream( void *ptr ) +static void *jackStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; @@ -2423,12 +2493,12 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } @@ -2437,6 +2507,7 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) @@ -2556,11 +2627,11 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) #include "asiodrivers.h" #include -AsioDrivers drivers; -ASIOCallbacks asioCallbacks; -ASIODriverInfo driverInfo; -CallbackInfo *asioCallbackInfo; -bool asioXRun; +static AsioDrivers drivers; +static ASIOCallbacks asioCallbacks; +static ASIODriverInfo driverInfo; +static CallbackInfo *asioCallbackInfo; +static bool asioXRun; struct AsioHandle { int drainCounter; // Tracks callback counts when draining @@ -2574,8 +2645,8 @@ struct AsioHandle { // Function declarations (definitions at end of section) static const char* getAsioErrorString( ASIOError result ); -void sampleRateChanged( ASIOSampleRate sRate ); -long asioMessages( long selector, long value, void* message, double* opt ); +static void sampleRateChanged( ASIOSampleRate sRate ); +static long asioMessages( long selector, long value, void* message, double* opt ); RtApiAsio :: RtApiAsio() { @@ -2586,7 +2657,7 @@ RtApiAsio :: RtApiAsio() HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } coInitialized_ = true; @@ -2617,19 +2688,21 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } if ( device >= nDevices ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } // If a stream is already open, we cannot probe other devices. Thus, use the saved results. if ( stream_.state != STREAM_CLOSED ) { if ( device >= devices_.size() ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } return devices_[ device ]; @@ -2640,7 +2713,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2649,7 +2722,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2657,7 +2730,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2668,7 +2741,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2695,7 +2768,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2708,6 +2781,8 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) info.nativeFormats |= RTAUDIO_FLOAT32; else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) info.nativeFormats |= RTAUDIO_FLOAT64; + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) + info.nativeFormats |= RTAUDIO_SINT24; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; @@ -2719,7 +2794,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) return info; } -void bufferSwitch( long index, ASIOBool processNow ) +static void bufferSwitch( long index, ASIOBool /*processNow*/ ) { RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; object->callbackEvent( index ); @@ -2860,6 +2935,10 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; } + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; + } if ( stream_.deviceFormat[mode] == 0 ) { drivers.removeCurrentDriver(); @@ -3049,7 +3128,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; errorText_ = errorStream_.str(); - error( RtError::WARNING); // warn but don't fail + error( RtAudioError::WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; @@ -3095,7 +3174,7 @@ void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -3138,7 +3217,7 @@ void RtApiAsio :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAsio::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -3160,7 +3239,7 @@ void RtApiAsio :: startStream() stopThreadCalled = false; if ( result == ASE_OK ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: stopStream() @@ -3168,7 +3247,7 @@ void RtApiAsio :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -3189,7 +3268,7 @@ void RtApiAsio :: stopStream() } if ( result == ASE_OK ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: abortStream() @@ -3197,7 +3276,7 @@ void RtApiAsio :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -3215,7 +3294,7 @@ void RtApiAsio :: abortStream() // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the ASIOStop() // function will return. -extern "C" unsigned __stdcall asioStopStream( void *ptr ) +static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; @@ -3230,7 +3309,7 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } @@ -3378,7 +3457,7 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) return SUCCESS; } -void sampleRateChanged( ASIOSampleRate sRate ) +static void sampleRateChanged( ASIOSampleRate sRate ) { // The ASIO documentation says that this usually only happens during // external sync. Audio processing is not stopped by the driver, @@ -3390,7 +3469,7 @@ void sampleRateChanged( ASIOSampleRate sRate ) try { object->stopStream(); } - catch ( RtError &exception ) { + catch ( RtAudioError &exception ) { std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; return; } @@ -3398,7 +3477,7 @@ void sampleRateChanged( ASIOSampleRate sRate ) std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; } -long asioMessages( long selector, long value, void* message, double* opt ) +static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) { long ret = 0; @@ -3476,7 +3555,7 @@ static const char* getAsioErrorString( ASIOError result ) const char*message; }; - static Messages m[] = + static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, @@ -3492,10 +3571,1342 @@ static const char* getAsioErrorString( ASIOError result ) return "Unknown error."; } + //******************** End of __WINDOWS_ASIO__ *********************// #endif +#if defined(__WINDOWS_WASAPI__) // Windows WASAPI API + +#include +#include +#include +#include + +//============================================================================= + +#define EXIT_ON_ERROR( hr, errorType, errorText )\ +if ( FAILED( hr ) )\ +{\ + errorText_ = __FUNCTION__ ": " errorText;\ + error( errorType );\ + goto Exit;\ +} + +#define SAFE_RELEASE( objectPtr )\ +if ( objectPtr )\ +{\ + objectPtr->Release();\ + objectPtr = NULL;\ +} + +typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); + +//----------------------------------------------------------------------------- + +// WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. +// Therefore we must perform all necessary conversions to user buffers in order to satisfy these +// requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to +// provide intermediate storage for read / write synchronization. +class WasapiBuffer +{ +public: + WasapiBuffer() + : buffer_( NULL ), + bufferSize_( 0 ), + inIndex_( 0 ), + outIndex_( 0 ) {} + + ~WasapiBuffer() { + delete buffer_; + } + + // sets the length of the internal ring buffer + void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { + delete buffer_; + + buffer_ = ( char* ) calloc( bufferSize, formatBytes ); + + bufferSize_ = bufferSize; + inIndex_ = 0; + outIndex_ = 0; + } + + // attempt to push a buffer into the ring buffer at the current "in" index + bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relOutIndex = outIndex_; + unsigned int inIndexEnd = inIndex_ + bufferSize; + if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { + relOutIndex += bufferSize_; + } + + // "in" index can end on the "out" index but cannot begin at it + if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { + return false; // not enough space between "in" index and "out" index + } + + // copy buffer from external to internal + int fromZeroSize = inIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromInSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); + memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); + memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); + memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); + memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); + memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); + memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); + break; + } + + // update "in" index + inIndex_ += bufferSize; + inIndex_ %= bufferSize_; + + return true; + } + + // attempt to pull a buffer from the ring buffer from the current "out" index + bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relInIndex = inIndex_; + unsigned int outIndexEnd = outIndex_ + bufferSize; + if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { + relInIndex += bufferSize_; + } + + // "out" index can begin at and end on the "in" index + if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { + return false; // not enough space between "out" index and "in" index + } + + // copy buffer from internal to external + int fromZeroSize = outIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromOutSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); + memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); + memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); + memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); + memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); + memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); + memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); + break; + } + + // update "out" index + outIndex_ += bufferSize; + outIndex_ %= bufferSize_; + + return true; + } + +private: + char* buffer_; + unsigned int bufferSize_; + unsigned int inIndex_; + unsigned int outIndex_; +}; + +//----------------------------------------------------------------------------- + +// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate and +// channel counts between HW and the user. The convertBufferWasapi function is used to perform +// these conversions between HwIn->UserIn and UserOut->HwOut during the stream callback loop. +// This sample rate converter favors speed over quality, and works best with conversions between +// one rate and its multiple. RtApiWasapi will not populate a device's sample rate list with rates +// that may cause artifacts via this conversion. +void convertBufferWasapi( char* outBuffer, + const char* inBuffer, + const unsigned int& inChannelCount, + const unsigned int& outChannelCount, + const unsigned int& inSampleRate, + const unsigned int& outSampleRate, + const unsigned int& inSampleCount, + unsigned int& outSampleCount, + const RtAudioFormat& format ) +{ + // calculate the new outSampleCount and relative sampleStep + float sampleRatio = ( float ) outSampleRate / inSampleRate; + float sampleStep = 1.0f / sampleRatio; + float inSampleFraction = 0.0f; + unsigned int commonChannelCount = min( inChannelCount, outChannelCount ); + + outSampleCount = ( unsigned int ) ( inSampleCount * sampleRatio ); + + // frame-by-frame, copy each relative input sample into it's corresponding output sample + for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) + { + unsigned int inSample = ( unsigned int ) inSampleFraction; + + switch ( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) outBuffer )[ outSample * outChannelCount ], &( ( char* ) inBuffer )[ inSample * inChannelCount ], commonChannelCount * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) outBuffer )[ outSample * outChannelCount ], &( ( short* ) inBuffer )[ inSample * inChannelCount ], commonChannelCount * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) outBuffer )[ outSample * outChannelCount ], &( ( S24* ) inBuffer )[ inSample * inChannelCount ], commonChannelCount * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) outBuffer )[ outSample * outChannelCount ], &( ( int* ) inBuffer )[ inSample * inChannelCount ], commonChannelCount * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) outBuffer )[ outSample * outChannelCount ], &( ( float* ) inBuffer )[ inSample * inChannelCount ], commonChannelCount * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) outBuffer )[ outSample * outChannelCount ], &( ( double* ) inBuffer )[ inSample * inChannelCount ], commonChannelCount * sizeof( double ) ); + break; + } + + // jump to next in sample + inSampleFraction += sampleStep; + } +} + +//----------------------------------------------------------------------------- + +// A structure to hold various information related to the WASAPI implementation. +struct WasapiHandle +{ + IAudioClient* captureAudioClient; + IAudioClient* renderAudioClient; + IAudioCaptureClient* captureClient; + IAudioRenderClient* renderClient; + HANDLE captureEvent; + HANDLE renderEvent; + + WasapiHandle() + : captureAudioClient( NULL ), + renderAudioClient( NULL ), + captureClient( NULL ), + renderClient( NULL ), + captureEvent( NULL ), + renderEvent( NULL ) {} +}; + +//============================================================================= + +RtApiWasapi::RtApiWasapi() + : coInitialized_( false ), deviceEnumerator_( NULL ) +{ + // WASAPI can run either apartment or multi-threaded + HRESULT hr = CoInitialize( NULL ); + + if ( !FAILED( hr ) ) + coInitialized_ = true; + + // instantiate device enumerator + hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, + CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), + ( void** ) &deviceEnumerator_ ); + + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; + error( RtAudioError::DRIVER_ERROR ); + } +} + +//----------------------------------------------------------------------------- + +RtApiWasapi::~RtApiWasapi() +{ + // if this object previously called CoInitialize() + if ( coInitialized_ ) { + CoUninitialize(); + } + + if ( stream_.state != STREAM_CLOSED ) { + closeStream(); + } + + SAFE_RELEASE( deviceEnumerator_ ); +} + +//============================================================================= + +unsigned int RtApiWasapi::getDeviceCount( void ) +{ + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + + // count capture devices + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device collection" ); + + hr = captureDevices->GetCount( &captureDeviceCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device count" ); + + // count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device collection" ); + + hr = renderDevices->GetCount( &renderDeviceCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device count" ); + +Exit: + // release all references + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + + return captureDeviceCount + renderDeviceCount; +} + +//----------------------------------------------------------------------------- + +RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + std::wstring deviceName; + std::string defaultDeviceName; + bool isCaptureDevice = false; + + PROPVARIANT deviceNameProp; + PROPVARIANT defaultDeviceNameProp; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + IMMDevice* defaultDevicePtr = NULL; + IAudioClient* audioClient = NULL; + IPropertyStore* devicePropStore = NULL; + IPropertyStore* defaultDevicePropStore = NULL; + + WAVEFORMATEX* deviceFormat = NULL; + WAVEFORMATEX* closestMatchFormat = NULL; + + // probed + info.probed = false; + + // count capture devices + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device collection" ); + + hr = captureDevices->GetCount( &captureDeviceCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device count" ); + + // count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device collection" ); + + hr = renderDevices->GetCount( &renderDeviceCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device count" ); + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) + EXIT_ON_ERROR( -1, RtAudioError::INVALID_USE, "Invalid device index" ); + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device handle" ); + + isCaptureDevice = true; + } + else { + hr = renderDevices->Item( device, &devicePtr ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device handle" ); + + isCaptureDevice = false; + } + + // get default device name + if ( isCaptureDevice ) { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve default render device handle" ); + } + else { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve default capture device handle" ); + } + + hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to open default device property store" ); + + PropVariantInit( &defaultDeviceNameProp ); + + hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve default device property: PKEY_Device_FriendlyName" ); + + deviceName = defaultDeviceNameProp.pwszVal; + defaultDeviceName = std::string( deviceName.begin(), deviceName.end() ); + + // name + hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to open device property store" ); + + PropVariantInit( &deviceNameProp ); + + hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device property: PKEY_Device_FriendlyName" ); + + deviceName = deviceNameProp.pwszVal; + info.name = std::string( deviceName.begin(), deviceName.end() ); + + // is default + if ( isCaptureDevice ) { + info.isDefaultInput = info.name == defaultDeviceName; + info.isDefaultOutput = false; + } + else { + info.isDefaultInput = false; + info.isDefaultOutput = info.name == defaultDeviceName; + } + + // channel count + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device audio client" ); + + hr = audioClient->GetMixFormat( &deviceFormat ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device mix format" ); + + if ( isCaptureDevice ) { + info.inputChannels = deviceFormat->nChannels; + info.outputChannels = 0; + info.duplexChannels = 0; + } + else { + info.inputChannels = 0; + info.outputChannels = deviceFormat->nChannels; + info.duplexChannels = 0; + } + + // sample rates + info.sampleRates.clear(); + + // allow support for sample rates that are multiples of the base rate + for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { + if ( SAMPLE_RATES[i] < deviceFormat->nSamplesPerSec ) { + if ( deviceFormat->nSamplesPerSec % SAMPLE_RATES[i] == 0 ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + } + else { + if ( SAMPLE_RATES[i] % deviceFormat->nSamplesPerSec == 0 ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + } + } + + // native format + info.nativeFormats = 0; + + if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) + { + if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_FLOAT32; + } + else if ( deviceFormat->wBitsPerSample == 64 ) { + info.nativeFormats |= RTAUDIO_FLOAT64; + } + } + else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) + { + if ( deviceFormat->wBitsPerSample == 8 ) { + info.nativeFormats |= RTAUDIO_SINT8; + } + else if ( deviceFormat->wBitsPerSample == 16 ) { + info.nativeFormats |= RTAUDIO_SINT16; + } + else if ( deviceFormat->wBitsPerSample == 24 ) { + info.nativeFormats |= RTAUDIO_SINT24; + } + else if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_SINT32; + } + } + + // probed + info.probed = true; + +Exit: + // release all references + PropVariantClear( &deviceNameProp ); + PropVariantClear( &defaultDeviceNameProp ); + + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + SAFE_RELEASE( defaultDevicePtr ); + SAFE_RELEASE( audioClient ); + SAFE_RELEASE( devicePropStore ); + SAFE_RELEASE( defaultDevicePropStore ); + + CoTaskMemFree( deviceFormat ); + CoTaskMemFree( closestMatchFormat ); + + 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 ) { + errorText_ = "RtApiWasapi::closeStream: No open stream to close"; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state != STREAM_STOPPED ) + stopStream(); + + // clean up stream memory + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) + + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); + + delete stream_.apiHandle; + stream_.apiHandle = NULL; + + for ( int i = 0; i < 2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // update stream state + stream_.state = STREAM_CLOSED; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::startStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiWasapi::startStream: The stream is already running"; + error( RtAudioError::WARNING ); + return; + } + + // update stream state + stream_.state = STREAM_RUNNING; + + // create WASAPI stream thread + stream_.callbackInfo.thread = ( unsigned int ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); + + if ( !stream_.callbackInfo.thread ) { + errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread"; + error( RtAudioError::THREAD_ERROR ); + } + else { + SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); + ResumeThread( ( void* ) stream_.callbackInfo.thread ); + } +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::stopStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::stopStream: The stream is already stopped"; + 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 ); + + // stop capture client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream"; + error( RtAudioError::DRIVER_ERROR ); + } + } + + // stop render client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream"; + error( RtAudioError::DRIVER_ERROR ); + } + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread"; + error( RtAudioError::THREAD_ERROR ); + } + + stream_.callbackInfo.thread = NULL; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::abortStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::abortStream: The stream is already stopped"; + 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 ); + } + + // stop capture client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream"; + error( RtAudioError::DRIVER_ERROR ); + } + } + + // stop render client if applicable + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { + HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream"; + error( RtAudioError::DRIVER_ERROR ); + } + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread"; + error( RtAudioError::THREAD_ERROR ); + } + + stream_.callbackInfo.thread = NULL; +} + +//----------------------------------------------------------------------------- + +bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ) +{ + bool methodResult = FAILURE; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + WAVEFORMATEX* deviceFormat = NULL; + + // create API Handle if not already created + if ( !stream_.apiHandle ) + stream_.apiHandle = ( void* ) new WasapiHandle(); + + // count capture devices + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device collection" ); + + hr = captureDevices->GetCount( &captureDeviceCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device count" ); + + // count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device collection" ); + + hr = renderDevices->GetCount( &renderDeviceCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device count" ); + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) + EXIT_ON_ERROR( -1, RtAudioError::INVALID_USE, "Invalid device index" ); + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + if ( mode != INPUT ) + EXIT_ON_ERROR( -1, RtAudioError::INVALID_USE, "Capture device selected as output device" ); + + // retrieve captureAudioClient from devicePtr + IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture device handle" ); + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device audio client" ); + + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device mix format" ); + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + else { + if ( mode != OUTPUT ) + EXIT_ON_ERROR( -1, RtAudioError::INVALID_USE, "Render device selected as input device" ); + + // retrieve renderAudioClient from devicePtr + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + + hr = renderDevices->Item( device, &devicePtr ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render device handle" ); + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &renderAudioClient ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device audio client" ); + + hr = renderAudioClient->GetMixFormat( &deviceFormat ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device mix format" ); + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // fill stream data + if ( ( stream_.mode == OUTPUT && mode == INPUT ) || + ( stream_.mode == INPUT && mode == OUTPUT ) ) { + stream_.mode = DUPLEX; + } + else { + stream_.mode = mode; + } + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + stream_.doByteSwap[mode] = false; + stream_.sampleRate = sampleRate; + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + stream_.userFormat = format; + stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + else + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + if ( stream_.doConvertBuffer[mode] ) + setConvertInfo( mode, 0 ); + + // Allocate necessary internal buffers + unsigned int bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); + + stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); + if ( !stream_.userBuffer[mode] ) + EXIT_ON_ERROR( -1, RtAudioError::MEMORY_ERROR, "Error allocating user buffer memory" ); + + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) + stream_.callbackInfo.priority = 15; + else + stream_.callbackInfo.priority = 0; + + ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback + ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode + + methodResult = SUCCESS; + +Exit: + //clean up + + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + + CoTaskMemFree( deviceFormat ); + + // if method failed, close the stream + if ( methodResult == FAILURE ) + closeStream(); + + return methodResult; +} + +//============================================================================= + +DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::wasapiThread() +{ + // as this is a new thread, we must CoInitialize it + CoInitialize( NULL ); + + HRESULT hr; + + IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; + IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; + HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; + HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; + + WAVEFORMATEX* captureFormat = NULL; + WAVEFORMATEX* renderFormat = NULL; + float captureSrRatio = 0.0f; + float renderSrRatio = 0.0f; + WasapiBuffer captureBuffer; + WasapiBuffer renderBuffer; + + // Attempt to assign "Pro Audio" characteristic to thread + HMODULE AvrtDll = LoadLibrary( "AVRT.dll" ); + if ( AvrtDll ) { + DWORD taskIndex = 0; + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); + FreeLibrary( AvrtDll ); + } + + // start capture stream if applicable + if ( captureAudioClient ) { + hr = captureAudioClient->GetMixFormat( &captureFormat ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device mix format" ); + + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + + // initialize capture stream according to desire buffer size + float desiredBufferSize = stream_.bufferSize * captureSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); + + if ( !captureClient ) { + hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + desiredBufferPeriod, + desiredBufferPeriod, + captureFormat, + NULL ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to initialize capture audio client" ); + + hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), + ( void** ) &captureClient ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture client handle" ); + + // configure captureEvent to trigger on every available capture buffer + captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !captureEvent ) + EXIT_ON_ERROR( -1, RtAudioError::SYSTEM_ERROR, "Unable to create capture event" ); + + hr = captureAudioClient->SetEventHandle( captureEvent ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to set capture event handle" ); + + ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; + ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; + } + + unsigned int inBufferSize = 0; + hr = captureAudioClient->GetBufferSize( &inBufferSize ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to get capture buffer size" ); + + // scale outBufferSize according to stream->user sample rate ratio + unsigned int outBufferSize = ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; + inBufferSize *= stream_.nDeviceChannels[INPUT]; + + // set captureBuffer size + captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); + + // reset the capture stream + hr = captureAudioClient->Reset(); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to reset capture stream" ); + + // start the capture stream + hr = captureAudioClient->Start(); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to start capture stream" ); + } + + // start render stream if applicable + if ( renderAudioClient ) { + hr = renderAudioClient->GetMixFormat( &renderFormat ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve device mix format" ); + + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + + // initialize render stream according to desire buffer size + float desiredBufferSize = stream_.bufferSize * renderSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); + + if ( !renderClient ) { + hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + desiredBufferPeriod, + desiredBufferPeriod, + renderFormat, + NULL ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to initialize render audio client" ); + + hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), + ( void** ) &renderClient ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render client handle" ); + + // configure renderEvent to trigger on every available render buffer + renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !renderEvent ) + EXIT_ON_ERROR( -1, RtAudioError::SYSTEM_ERROR, "Unable to create render event" ); + + hr = renderAudioClient->SetEventHandle( renderEvent ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to set render event handle" ); + + ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; + ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + } + + unsigned int outBufferSize = 0; + hr = renderAudioClient->GetBufferSize( &outBufferSize ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to get render buffer size" ); + + // scale inBufferSize according to user->stream sample rate ratio + unsigned int inBufferSize = ( unsigned int ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; + outBufferSize *= stream_.nDeviceChannels[OUTPUT]; + + // set renderBuffer size + renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); + + // reset the render stream + hr = renderAudioClient->Reset(); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to reset render stream" ); + + // start the render stream + hr = renderAudioClient->Start(); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to start render stream" ); + } + + // declare local stream variables + RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; + + BYTE* streamBuffer = NULL; + unsigned long captureFlags = 0; + + unsigned int bufferFrameCount = 0; + unsigned int numFramesPadding = 0; + unsigned int convBufferSize = 0; + + bool callbackPushed = false; + bool callbackPulled = false; + bool callbackStopped = false; + + int callbackResult = 0; + + // convBuffer is used to store converted buffers between WASAPI and the user + unsigned int deviceBufferSize = 0; + if ( stream_.mode == INPUT ) { + deviceBufferSize = ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + } + else if ( stream_.mode == OUTPUT ) { + deviceBufferSize = ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + } + else if ( stream_.mode == DUPLEX ) { + deviceBufferSize = max( ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + } + + char* convBuffer = ( char* ) malloc( deviceBufferSize ); + stream_.deviceBuffer = ( char* ) malloc( deviceBufferSize ); + if ( !convBuffer || !stream_.deviceBuffer ) + EXIT_ON_ERROR( -1, RtAudioError::MEMORY_ERROR, "Error allocating device buffer memory" ); + + // stream process loop + while ( stream_.state != STREAM_STOPPING ) { + if ( !callbackPulled ) { + // Callback Input + // ============== + // 1. Pull callback buffer from inputBuffer + // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count + // Convert callback buffer to user format + + if ( captureAudioClient ) { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( callbackPulled ) { + // Convert callback buffer to user sample rate and channel count + convertBufferWasapi( stream_.deviceBuffer, + convBuffer, + stream_.nDeviceChannels[INPUT], + stream_.nUserChannels[INPUT], + captureFormat->nSamplesPerSec, + stream_.sampleRate, + ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), + convBufferSize, + stream_.deviceFormat[INPUT] ); + + if ( stream_.doConvertBuffer[INPUT] ) { + // Convert callback buffer to user format + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + else { + // no conversion, simple copy deviceBuffer to userBuffer + memcpy( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); + } + } + } + else { + // if there is no capture stream, set callbackPulled flag + callbackPulled = true; + } + + // Execute Callback + // ================ + // 1. Execute user callback method + // 2. Handle return value from callback + + // if callback has not requested the stream to stop + if ( callbackPulled && !callbackStopped ) { + // Execute user callback method + callbackResult = callback( stream_.userBuffer[OUTPUT], + stream_.userBuffer[INPUT], + stream_.bufferSize, + getStreamTime(), + captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, + stream_.callbackInfo.userData ); + + // Handle return value from callback + if ( callbackResult == 1 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, NULL, NULL ); + + if ( !threadHandle ) { + EXIT_ON_ERROR( -1, RtAudioError::THREAD_ERROR, "Unable to instantiate stream stop thread" ); + } + else if ( !CloseHandle( threadHandle ) ) { + EXIT_ON_ERROR( -1, RtAudioError::THREAD_ERROR, "Unable to close stream stop thread handle" ); + } + + callbackStopped = true; + } + else if ( callbackResult == 2 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, NULL, NULL ); + + if ( !threadHandle ) { + EXIT_ON_ERROR( -1, RtAudioError::THREAD_ERROR, "Unable to instantiate stream abort thread" ); + } + else if ( !CloseHandle( threadHandle ) ) { + EXIT_ON_ERROR( -1, RtAudioError::THREAD_ERROR, "Unable to close stream abort thread handle" ); + } + + callbackStopped = true; + } + } + } + + // Callback Output + // =============== + // 1. Convert callback buffer to stream format + // 2. Convert callback buffer to stream sample rate and channel count + // 3. Push callback buffer into outputBuffer + + if ( renderAudioClient && callbackPulled ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + + // Convert callback buffer to stream sample rate and channel count + convertBufferWasapi( convBuffer, + stream_.deviceBuffer, + stream_.nUserChannels[OUTPUT], + stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, + renderFormat->nSamplesPerSec, + stream_.bufferSize, + convBufferSize, + stream_.deviceFormat[OUTPUT] ); + } + else { + // Convert callback buffer to stream sample rate and channel count + convertBufferWasapi( convBuffer, + stream_.userBuffer[OUTPUT], + stream_.nUserChannels[OUTPUT], + stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, + renderFormat->nSamplesPerSec, + stream_.bufferSize, + convBufferSize, + stream_.deviceFormat[OUTPUT] ); + } + + // Push callback buffer into outputBuffer + callbackPushed = renderBuffer.pushBuffer( convBuffer, + convBufferSize * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ); + } + + // Stream Capture + // ============== + // 1. Get capture buffer from stream + // 2. Push capture buffer into inputBuffer + // 3. If 2. was successful: Release capture buffer + + if ( captureAudioClient ) { + // if the callback input buffer was not pulled from captureBuffer, wait for next capture event + if ( !callbackPulled ) { + WaitForSingleObject( captureEvent, INFINITE ); + } + + // Get capture buffer from stream + hr = captureClient->GetBuffer( &streamBuffer, + &bufferFrameCount, + &captureFlags, NULL, NULL ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve capture buffer" ); + + if ( bufferFrameCount != 0 ) { + // Push capture buffer into inputBuffer + if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ) ) + { + // Release capture buffer + hr = captureClient->ReleaseBuffer( bufferFrameCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to release capture buffer" ); + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to release capture buffer" ); + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to release capture buffer" ); + } + } + + // Stream Render + // ============= + // 1. Get render buffer from stream + // 2. Pull next buffer from outputBuffer + // 3. If 2. was successful: Fill render buffer with next buffer + // Release render buffer + + if ( renderAudioClient ) { + // if the callback output buffer was not pushed to renderBuffer, wait for next render event + if ( callbackPulled && !callbackPushed ) { + WaitForSingleObject( renderEvent, INFINITE ); + } + + // Get render buffer from stream + hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render buffer size" ); + + hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render buffer padding" ); + + bufferFrameCount -= numFramesPadding; + + if ( bufferFrameCount != 0 ) { + hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to retrieve render buffer" ); + + // Pull next buffer from outputBuffer + // Fill render buffer with next buffer + if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ) ) + { + // Release render buffer + hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to release render buffer" ); + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to release render buffer" ); + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + EXIT_ON_ERROR( hr, RtAudioError::DRIVER_ERROR, "Unable to release render buffer" ); + } + } + + // if the callback buffer was pushed renderBuffer reset callbackPulled flag + if ( callbackPushed ) { + callbackPulled = false; + } + + // tick stream time + RtApi::tickStreamTime(); + } + +Exit: + // clean up + CoTaskMemFree( captureFormat ); + CoTaskMemFree( renderFormat ); + + //delete convBuffer; + free ( convBuffer ); + + CoUninitialize(); + + // update stream state + stream_.state = STREAM_STOPPED; +} + +//******************** End of __WINDOWS_WASAPI__ *********************// +#endif + + #if defined(__WINDOWS_DS__) // Windows DirectSound API // Modified by Robin Davies, October 2005 @@ -3557,7 +4968,7 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, static const char* getErrorString( int code ); -extern "C" unsigned __stdcall callbackHandler( void *ptr ); +static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { LPGUID id[2]; @@ -3569,7 +4980,10 @@ struct DsDevice { : found(false) { validId[0] = false; validId[1] = false; } }; -std::vector< DsDevice > dsDevices; +struct DsProbeData { + bool isInput; + std::vector* dsDevices; +}; RtApiDs :: RtApiDs() { @@ -3607,32 +5021,35 @@ unsigned int RtApiDs :: getDeviceCount( void ) dsDevices[i].found = false; // Query DirectSound devices. - bool isInput = false; - HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &isInput ); + struct DsProbeData probeInfo; + probeInfo.isInput = false; + probeInfo.dsDevices = &dsDevices; + HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating output devices!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } // Query DirectSoundCapture devices. - isInput = true; - result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &isInput ); + probeInfo.isInput = true; + result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating input devices!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } // Clean out any devices that may have disappeared. std::vector< int > indices; for ( unsigned int i=0; i(dsDevices.size()); } RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) @@ -3645,13 +5062,15 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) getDeviceCount(); if ( dsDevices.size() == 0 ) { errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } } if ( device >= dsDevices.size() ) { errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } HRESULT result; @@ -3663,7 +5082,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto probeInput; } @@ -3673,7 +5092,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) output->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto probeInput; } @@ -3710,7 +5129,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -3721,7 +5140,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) input->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -3818,7 +5237,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned return FAILURE; } - unsigned int nDevices = dsDevices.size(); + size_t nDevices = dsDevices.size(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; @@ -3879,7 +5298,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. - DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE;; + DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; @@ -4306,6 +5725,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -4313,7 +5733,7 @@ void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4368,7 +5788,7 @@ void RtApiDs :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4416,7 +5836,7 @@ void RtApiDs :: startStream() stream_.state = STREAM_RUNNING; unlock: - if ( FAILED( result ) ) error( RtError::SYSTEM_ERROR ); + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: stopStream() @@ -4424,7 +5844,7 @@ void RtApiDs :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4513,7 +5933,7 @@ void RtApiDs :: stopStream() unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. - if ( FAILED( result ) ) error( RtError::SYSTEM_ERROR ); + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: abortStream() @@ -4521,7 +5941,7 @@ void RtApiDs :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4540,7 +5960,7 @@ void RtApiDs :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4626,26 +6046,30 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } while ( true ) { result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; Sleep( 1 ); @@ -4665,7 +6089,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; @@ -4715,7 +6140,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } // We will copy our output buffer into the region between @@ -4755,7 +6181,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } // Copy our buffer into the DS buffer @@ -4767,7 +6194,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; handle->bufferPointer[0] = nextWritePointer; @@ -4801,7 +6229,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset @@ -4861,7 +6290,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset @@ -4874,7 +6304,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( duplexPrerollBytes <= 0 ) { @@ -4894,7 +6325,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } handle->bufferPointer[1] = nextReadPointer; @@ -4916,7 +6348,7 @@ void RtApiDs :: callbackEvent() // Definitions for utility functions and callbacks // specific to the DirectSound implementation. -extern "C" unsigned __stdcall callbackHandler( void *ptr ) +static unsigned __stdcall callbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiDs *object = (RtApiDs *) info->object; @@ -4932,12 +6364,12 @@ extern "C" unsigned __stdcall callbackHandler( void *ptr ) #include "tchar.h" -std::string convertTChar( LPCTSTR name ) +static std::string convertTChar( LPCTSTR name ) { #if defined( UNICODE ) || defined( _UNICODE ) int length = WideCharToMultiByte(CP_UTF8, 0, name, -1, NULL, 0, NULL, NULL); - std::string s( length, 0 ); - length = WideCharToMultiByte(CP_UTF8, 0, name, wcslen(name), &s[0], length, NULL, NULL); + std::string s( length-1, '\0' ); + WideCharToMultiByte(CP_UTF8, 0, name, -1, &s[0], length, NULL, NULL); #else std::string s( name ); #endif @@ -4947,14 +6379,15 @@ std::string convertTChar( LPCTSTR name ) static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, - LPCTSTR module, + LPCTSTR /*module*/, LPVOID lpContext ) { - bool *isInput = (bool *) lpContext; + struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; + std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; - if ( *isInput == true ) { + if ( probeInfo.isInput == true ) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; @@ -4986,13 +6419,14 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, // If good device, then save its name and guid. std::string name = convertTChar( description ); - if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + if ( lpguid == NULL ) name = "Default Device"; if ( validDevice ) { for ( unsigned int i=0; i= nDevices ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } foundDevice: @@ -5210,7 +6661,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_ctl_close( chandle ); if ( device >= devices_.size() ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } return devices_[ device ]; @@ -5224,23 +6675,25 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); - // First try for playback + // First try for playback unless default device (which has subdev -1) stream = SND_PCM_STREAM_PLAYBACK; - snd_pcm_info_set_device( pcminfo, subdevice ); - snd_pcm_info_set_subdevice( pcminfo, 0 ); snd_pcm_info_set_stream( pcminfo, stream ); + if ( subdevice != -1 ) { + snd_pcm_info_set_device( pcminfo, subdevice ); + snd_pcm_info_set_subdevice( pcminfo, 0 ); - result = snd_ctl_pcm_info( chandle, pcminfo ); - if ( result < 0 ) { - // Device probably doesn't support playback. - goto captureProbe; + result = snd_ctl_pcm_info( chandle, pcminfo ); + if ( result < 0 ) { + // Device probably doesn't support playback. + goto captureProbe; + } } result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto captureProbe; } @@ -5250,7 +6703,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto captureProbe; } @@ -5261,30 +6714,34 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto captureProbe; } info.outputChannels = value; snd_pcm_close( phandle ); captureProbe: - // Now try for capture stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); - result = snd_ctl_pcm_info( chandle, pcminfo ); - snd_ctl_close( chandle ); - if ( result < 0 ) { - // Device probably doesn't support capture. - if ( info.outputChannels == 0 ) return info; - goto probeParameters; + // Now try for capture unless default device (with subdev = -1) + if ( subdevice != -1 ) { + result = snd_ctl_pcm_info( chandle, pcminfo ); + snd_ctl_close( chandle ); + if ( result < 0 ) { + // Device probably doesn't support capture. + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } } + else + snd_ctl_close( chandle ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } @@ -5295,7 +6752,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } @@ -5305,7 +6762,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } @@ -5339,7 +6796,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -5349,7 +6806,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -5363,7 +6820,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -5391,17 +6848,20 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { + snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } // Get the device name char *cardname; result = snd_card_get_name( card, &cardname ); - if ( result >= 0 ) + if ( result >= 0 ) { sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } info.name = name; // That's all ... close the device and return @@ -5468,6 +6928,15 @@ 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" ); + goto foundDevice; + } + nDevices++; + } + if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; @@ -5848,7 +7317,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne apiInfo->synchronized = true; else { errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } } else { @@ -5866,22 +7335,21 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + #ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { - struct sched_param param; + // We previously attempted to increase the audio callback priority + // to SCHED_RR here via the attributes. However, while no errors + // were reported in doing so, it did not work. So, now this is + // done in the alsaCallbackHandler function. + stream_.callbackInfo.doRealtime = true; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; - param.sched_priority = priority; - pthread_attr_setschedparam( &attr, ¶m ); - pthread_attr_setschedpolicy( &attr, SCHED_RR ); + stream_.callbackInfo.priority = priority; } - else - pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); -#else - pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; @@ -5919,6 +7387,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -5926,7 +7395,7 @@ void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -5979,7 +7448,7 @@ void RtApiAlsa :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6002,6 +7471,7 @@ void RtApiAlsa :: startStream() } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open state = snd_pcm_state( handle[1] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[1] ); @@ -6021,7 +7491,7 @@ void RtApiAlsa :: startStream() MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: stopStream() @@ -6029,7 +7499,7 @@ void RtApiAlsa :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6061,10 +7531,11 @@ void RtApiAlsa :: stopStream() } unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: abortStream() @@ -6072,7 +7543,7 @@ void RtApiAlsa :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6101,10 +7572,11 @@ void RtApiAlsa :: abortStream() } unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: callbackEvent() @@ -6124,7 +7596,7 @@ void RtApiAlsa :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6207,7 +7679,7 @@ void RtApiAlsa :: callbackEvent() errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto tryOutput; } @@ -6277,7 +7749,7 @@ void RtApiAlsa :: callbackEvent() errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto unlock; } @@ -6293,12 +7765,20 @@ void RtApiAlsa :: callbackEvent() if ( doStopStream == 1 ) this->stopStream(); } -extern "C" void *alsaCallbackHandler( void *ptr ) +static void *alsaCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + if ( &info->doRealtime ) { + pthread_t tID = pthread_self(); // ID of this thread + sched_param prio = { info->priority }; // scheduling priority of thread + pthread_setschedparam( tID, SCHED_RR, &prio ); + } +#endif + while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); @@ -6319,9 +7799,8 @@ extern "C" void *alsaCallbackHandler( void *ptr ) #include #include -namespace { -const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, - 44100, 48000, 96000, 0}; } +static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, + 44100, 48000, 96000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; @@ -6354,7 +7833,7 @@ unsigned int RtApiPulse::getDeviceCount( void ) return 1; } -RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; info.probed = true; @@ -6373,7 +7852,7 @@ RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) return info; } -extern "C" void *pulseaudio_callback( void * user ) +static void *pulseaudio_callback( void * user ) { CallbackInfo *cbi = static_cast( user ); RtApiPulse *context = static_cast( cbi->object ); @@ -6445,14 +7924,14 @@ void RtApiPulse::callbackEvent( void ) if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " "this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; - int doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); @@ -6462,50 +7941,52 @@ void RtApiPulse::callbackEvent( void ) } MUTEX_LOCK( &stream_.mutex ); + void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; + void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; if ( stream_.state != STREAM_RUNNING ) goto unlock; int pa_error; size_t bytes; - switch ( stream_.mode ) { - case INPUT: - bytes = stream_.nUserChannels[1] * stream_.bufferSize * formatBytes( stream_.userFormat ); - if ( pa_simple_read( pah->s_rec, stream_.userBuffer[1], bytes, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - error( RtError::WARNING ); - } - break; - case OUTPUT: - bytes = stream_.nUserChannels[0] * stream_.bufferSize * formatBytes( stream_.userFormat ); - if ( pa_simple_write( pah->s_play, stream_.userBuffer[0], bytes, &pa_error ) < 0 ) { + if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[OUTPUT] ); + } else + bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } - break; - case DUPLEX: - bytes = stream_.nUserChannels[1] * stream_.bufferSize * formatBytes( stream_.userFormat ); - if ( pa_simple_read( pah->s_rec, stream_.userBuffer[1], bytes, &pa_error ) < 0 ) { + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { + if ( stream_.doConvertBuffer[INPUT] ) + bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[INPUT] ); + else + bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } - bytes = stream_.nUserChannels[0] * stream_.bufferSize * formatBytes( stream_.userFormat ); - if ( pa_simple_write( pah->s_play, stream_.userBuffer[0], bytes, &pa_error ) < 0) { - errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - error( RtError::WARNING ); + if ( stream_.doConvertBuffer[INPUT] ) { + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); } - break; - default: - // ERROR - break; } unlock: @@ -6522,12 +8003,12 @@ void RtApiPulse::startStream( void ) if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::startStream(): the stream is not open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiPulse::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6546,12 +8027,12 @@ void RtApiPulse::stopStream( void ) if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6565,7 +8046,8 @@ void RtApiPulse::stopStream( void ) pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } } @@ -6579,12 +8061,12 @@ void RtApiPulse::abortStream( void ) if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6598,7 +8080,8 @@ void RtApiPulse::abortStream( void ) pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } } @@ -6645,30 +8128,34 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, if ( format == sf->rtaudio_format ) { sf_found = true; stream_.userFormat = sf->rtaudio_format; + stream_.deviceFormat[mode] = stream_.userFormat; ss.format = sf->pa_format; break; } } - if ( !sf_found ) { - errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample format."; - return false; + if ( !sf_found ) { // Use internal data format conversion. + stream_.userFormat = format; + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + ss.format = PA_SAMPLE_FLOAT32LE; } - if ( options && ( options->flags & RTAUDIO_NONINTERLEAVED ) ) { - errorText_ = "RtApiPulse::probeDeviceOpen: only interleaved audio data supported."; - return false; - } - - stream_.userInterleaved = true; - stream_.nBuffers = 1; - + // Set other stream parameters. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; + stream_.nBuffers = 1; stream_.doByteSwap[mode] = false; - stream_.doConvertBuffer[mode] = false; - stream_.deviceFormat[mode] = stream_.userFormat; stream_.nUserChannels[mode] = channels; - stream_.nDeviceChannels[mode] = channels; + stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.channelOffset[mode] = 0; + std::string streamName = "RtAudio"; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); @@ -6679,6 +8166,33 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, } stream_.bufferSize = *bufferSize; + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + if ( !stream_.apiHandle ) { PulseAudioHandle *pah = new PulseAudioHandle; if ( !pah ) { @@ -6695,9 +8209,14 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, pah = static_cast( stream_.apiHandle ); int error; + if ( !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { case INPUT: - pah->s_rec = pa_simple_new( NULL, "RtAudio", PA_STREAM_RECORD, NULL, "Record", &ss, NULL, NULL, &error ); + pa_buffer_attr buffer_attr; + 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 ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; @@ -6721,8 +8240,6 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, else stream_.mode = DUPLEX; - stream_.state = STREAM_STOPPED; - if ( !stream_.callbackInfo.isRunning ) { stream_.callbackInfo.object = this; stream_.callbackInfo.isRunning = true; @@ -6731,11 +8248,30 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, goto error; } } + + stream_.state = STREAM_STOPPED; return true; error: - closeStream(); - return false; + if ( pah && stream_.callbackInfo.isRunning ) { + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; } //******************** End of __LINUX_PULSE__ *********************// @@ -6747,11 +8283,11 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, #include #include #include -#include "soundcard.h" +#include #include #include -extern "C" void *ossCallbackHandler(void * ptr); +static void *ossCallbackHandler(void * ptr); // A structure to hold various information related to the OSS API // implementation. @@ -6780,7 +8316,7 @@ unsigned int RtApiOss :: getDeviceCount( void ) int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -6788,7 +8324,7 @@ unsigned int RtApiOss :: getDeviceCount( void ) if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -6804,7 +8340,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6813,7 +8349,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6821,13 +8357,15 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( nDevices == 0 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } if ( device >= nDevices ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } oss_audioinfo ainfo; @@ -6837,7 +8375,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6866,7 +8404,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( info.nativeFormats == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6893,7 +8431,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } else { info.probed = true; @@ -7342,7 +8880,7 @@ void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7391,7 +8929,7 @@ void RtApiOss :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiOss::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7413,7 +8951,7 @@ void RtApiOss :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7450,7 +8988,7 @@ void RtApiOss :: stopStream() result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } } @@ -7477,7 +9015,7 @@ void RtApiOss :: stopStream() MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: abortStream() @@ -7485,7 +9023,7 @@ void RtApiOss :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7523,7 +9061,7 @@ void RtApiOss :: abortStream() MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() @@ -7541,7 +9079,7 @@ void RtApiOss :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7611,7 +9149,7 @@ void RtApiOss :: callbackEvent() // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); // Continue on to input section. } } @@ -7638,7 +9176,7 @@ void RtApiOss :: callbackEvent() // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto unlock; } @@ -7658,7 +9196,7 @@ void RtApiOss :: callbackEvent() if ( doStopStream == 1 ) this->stopStream(); } -extern "C" void *ossCallbackHandler( void *ptr ) +static void *ossCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiOss *object = (RtApiOss *) info->object; @@ -7684,20 +9222,41 @@ extern "C" void *ossCallbackHandler( void *ptr ) // This method can be modified to control the behavior of error // message printing. -void RtApi :: error( RtError::Type type ) +void RtApi :: error( RtAudioError::Type type ) { errorStream_.str(""); // clear the ostringstream - if ( type == RtError::WARNING && showWarnings_ == true ) + + RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; + if ( errorCallback ) { + // abortStream() can generate new error messages. Ignore them. Just keep original one. + + if ( firstErrorOccurred_ ) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorText_; + + if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { + stream_.callbackInfo.isRunning = false; // exit from the thread + abortStream(); + } + + errorCallback( type, errorMessage ); + firstErrorOccurred_ = false; + return; + } + + if ( type == RtAudioError::WARNING && showWarnings_ == true ) std::cerr << '\n' << errorText_ << "\n\n"; - else if ( type != RtError::WARNING ) - throw( RtError( errorText_, type ) ); + else if ( type != RtAudioError::WARNING ) + throw( RtAudioError( errorText_, type ) ); } void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApi:: a stream is not open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); } } @@ -7716,6 +9275,7 @@ void RtApi :: clearStreamInfo() stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; + stream_.callbackInfo.errorCallback = 0; for ( int i=0; i<2; i++ ) { stream_.device[i] = 11111; stream_.doConvertBuffer[i] = false; @@ -7741,16 +9301,17 @@ unsigned int RtApi :: formatBytes( RtAudioFormat format ) { if ( format == RTAUDIO_SINT16 ) return 2; - else if ( format == RTAUDIO_SINT24 || format == RTAUDIO_SINT32 || - format == RTAUDIO_FLOAT32 ) + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) return 4; else if ( format == RTAUDIO_FLOAT64 ) return 8; + else if ( format == RTAUDIO_SINT24 ) + return 3; else if ( format == RTAUDIO_SINT8 ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -7878,11 +9439,11 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } else if (info.inFormat == RTAUDIO_SINT24) { - Int32 *in = (Int32 *)inBuffer; + Int24 *in = (Int24 *)inBuffer; scale = 1.0 / 8388607.5; for (unsigned int i=0; i>= 8; + out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] >> 8); + //out[info.outOffset[j]] >>= 8; } in += info.inJump; out += info.outJump; @@ -8162,10 +9723,10 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } else if (info.inFormat == RTAUDIO_SINT24) { - Int32 *in = (Int32 *)inBuffer; + Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 8) & 0x0000ffff); + out[info.outOffset[j]] = (Int16) (in[info.inOffset[j]].asInt() >> 8); } in += info.inJump; out += info.outJump; @@ -8226,10 +9787,10 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } else if (info.inFormat == RTAUDIO_SINT24) { - Int32 *in = (Int32 *)inBuffer; + Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 16) & 0x000000ff); + out[info.outOffset[j]] = (signed char) (in[info.inOffset[j]].asInt() >> 16); } in += info.inJump; out += info.outJump; @@ -8268,9 +9829,9 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } - //static inline uint16_t bswap_16(uint16_t x) { return (x>>8) | (x<<8); } - //static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } - //static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } +//static inline uint16_t bswap_16(uint16_t x) { return (x>>8) | (x<<8); } +//static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } +//static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) { @@ -8289,8 +9850,7 @@ void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat ptr += 2; } } - else if ( format == RTAUDIO_SINT24 || - format == RTAUDIO_SINT32 || + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) { for ( unsigned int i=0; i @@ -45,6 +43,23 @@ // RtMidi Definitions //*********************************************************************// +RtMidi :: RtMidi() + : rtapi_(0) +{ +} + +RtMidi :: ~RtMidi() +{ + if ( rtapi_ ) + delete rtapi_; + rtapi_ = 0; +} + +std::string RtMidi :: getVersion( void ) throw() +{ + return std::string( RTMIDI_VERSION ); +} + void RtMidi :: getCompiledApi( std::vector &apis ) throw() { apis.clear(); @@ -63,30 +78,11 @@ void RtMidi :: getCompiledApi( std::vector &apis ) throw() #if defined(__WINDOWS_MM__) apis.push_back( WINDOWS_MM ); #endif -#if defined(__WINDOWS_KS__) - apis.push_back( WINDOWS_KS ); -#endif #if defined(__RTMIDI_DUMMY__) apis.push_back( RTMIDI_DUMMY ); #endif } -void RtMidi :: error( RtError::Type type, std::string errorString ) -{ - if (type == RtError::WARNING) { - std::cerr << '\n' << errorString << "\n\n"; - } - else if (type == RtError::DEBUG_WARNING) { -#if defined(__RTMIDI_DEBUG__) - std::cerr << '\n' << errorString << "\n\n"; -#endif - } - else { - std::cerr << '\n' << errorString << "\n\n"; - throw RtError( errorString, type ); - } -} - //*********************************************************************// // RtMidiIn Definitions //*********************************************************************// @@ -109,10 +105,6 @@ void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, uns if ( api == WINDOWS_MM ) rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); #endif -#if defined(__WINDOWS_KS__) - if ( api == WINDOWS_KS ) - rtapi_ = new MidiInWinKS( clientName, queueSizeLimit ); -#endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiInCore( clientName, queueSizeLimit ); @@ -124,17 +116,16 @@ void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, uns } RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) + : RtMidi() { - rtapi_ = 0; - if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openMidiApi( api, clientName, queueSizeLimit ); if ( rtapi_ ) return; - // No compiled support for specified API value. Issue a debug - // warning and continue as if no API was specified. - RtMidi::error( RtError::WARNING, "RtMidiIn: no compiled support for specified API argument!" ); + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find @@ -151,13 +142,13 @@ RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned in // It should not be possible to get here because the preprocessor // definition __RTMIDI_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in - // case something weird happens, we'll print out an error message. - RtMidi::error( RtError::WARNING, "RtMidiIn: no compiled API support found ... critical error!!" ); + // case something weird happens, we'll throw an error. + std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; + throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); } RtMidiIn :: ~RtMidiIn() throw() { - delete rtapi_; } @@ -183,10 +174,6 @@ void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName ) if ( api == WINDOWS_MM ) rtapi_ = new MidiOutWinMM( clientName ); #endif -#if defined(__WINDOWS_KS__) - if ( api == WINDOWS_KS ) - rtapi_ = new MidiOutWinKS( clientName ); -#endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiOutCore( clientName ); @@ -199,16 +186,14 @@ void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName ) RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName ) { - rtapi_ = 0; - if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openMidiApi( api, clientName ); if ( rtapi_ ) return; - // No compiled support for specified API value. Issue a debug - // warning and continue as if no API was specified. - RtMidi::error( RtError::WARNING, "RtMidiOut: no compiled support for specified API argument!" ); + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find @@ -225,13 +210,61 @@ RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName ) // It should not be possible to get here because the preprocessor // definition __RTMIDI_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in - // case something weird happens, we'll print out an error message. - RtMidi::error( RtError::WARNING, "RtMidiOut: no compiled API support found ... critical error!!" ); + // case something weird happens, we'll thrown an error. + std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; + throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); } RtMidiOut :: ~RtMidiOut() throw() { - delete rtapi_; +} + +//*********************************************************************// +// Common MidiApi Definitions +//*********************************************************************// + +MidiApi :: MidiApi( void ) + : apiData_( 0 ), connected_( false ), errorCallback_(0) +{ +} + +MidiApi :: ~MidiApi( void ) +{ +} + +void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback ) +{ + errorCallback_ = errorCallback; +} + +void MidiApi :: error( RtMidiError::Type type, std::string errorString ) +{ + if ( errorCallback_ ) { + static bool firstErrorOccured = false; + + if ( firstErrorOccured ) + return; + + firstErrorOccured = true; + const std::string errorMessage = errorString; + + errorCallback_( type, errorMessage ); + firstErrorOccured = false; + return; + } + + if ( type == RtMidiError::WARNING ) { + std::cerr << '\n' << errorString << "\n\n"; + } + else if ( type == RtMidiError::DEBUG_WARNING ) { +#if defined(__RTMIDI_DEBUG__) + std::cerr << '\n' << errorString << "\n\n"; +#endif + } + else { + std::cerr << '\n' << errorString << "\n\n"; + throw RtMidiError( errorString, type ); + } } //*********************************************************************// @@ -239,7 +272,7 @@ RtMidiOut :: ~RtMidiOut() throw() //*********************************************************************// MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) - : apiData_( 0 ), connected_( false ) + : MidiApi() { // Allocate the MIDI queue. inputData_.queue.ringSize = queueSizeLimit; @@ -257,17 +290,17 @@ void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData { if ( inputData_.usingCallback ) { errorString_ = "MidiInApi::setCallback: a callback function is already set!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } if ( !callback ) { errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } - inputData_.userCallback = (void *) callback; + inputData_.userCallback = callback; inputData_.userData = userData; inputData_.usingCallback = true; } @@ -276,7 +309,7 @@ void MidiInApi :: cancelCallback() { if ( !inputData_.usingCallback ) { errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -299,7 +332,7 @@ double MidiInApi :: getMessage( std::vector *message ) if ( inputData_.usingCallback ) { errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return 0.0; } @@ -322,7 +355,7 @@ double MidiInApi :: getMessage( std::vector *message ) //*********************************************************************// MidiOutApi :: MidiOutApi( void ) - : apiData_( 0 ), connected_( false ) + : MidiApi() { } @@ -363,7 +396,7 @@ struct CoreMidiData { // Class Definitions: MidiInCore //*********************************************************************// -void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef ) +static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) { MidiInApi::RtMidiInData *data = static_cast (procRef); CoreMidiData *apiData = static_cast (data->apiData); @@ -422,7 +455,7 @@ void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef } continueSysex = packet->data[nBytes-1] != 0xF7; - if ( !continueSysex ) { + if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { // If not a continuing sysex message, invoke the user callback function or queue the message. if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; @@ -537,7 +570,8 @@ void MidiInCore :: initialize( const std::string& clientName ) OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client ); if ( result != noErr ) { errorString_ = "MidiInCore::initialize: error creating OS-X MIDI client object."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Save our api-specific connection information. @@ -552,21 +586,24 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string portName { if ( connected_ ) { errorString_ = "MidiInCore::openPort: a valid connection already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nSrc = MIDIGetNumberOfSources(); if (nSrc < 1) { errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; - RtMidi::error( RtError::NO_DEVICES_FOUND, errorString_ ); + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; } - std::ostringstream ost; if ( portNumber >= nSrc ) { + std::ostringstream ost; ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; } MIDIPortRef port; @@ -577,7 +614,8 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string portName if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Get the desired input source identifier. @@ -586,7 +624,8 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string portName MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Make the connection. @@ -595,7 +634,8 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string portName MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Save our api-specific port information. @@ -615,7 +655,8 @@ void MidiInCore :: openVirtualPort( const std::string portName ) midiInputCallback, (void *)&inputData_, &endpoint ); if ( result != noErr ) { errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Save our api-specific connection information. @@ -633,6 +674,7 @@ void MidiInCore :: closePort( void ) unsigned int MidiInCore :: getPortCount() { + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); return MIDIGetNumberOfSources(); } @@ -651,7 +693,7 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) CFRelease( str ); } - MIDIEntityRef entity = NULL; + MIDIEntityRef entity = 0; MIDIEndpointGetEntity( endpoint, &entity ); if ( entity == 0 ) // probably virtual @@ -764,21 +806,21 @@ std::string MidiInCore :: getPortName( unsigned int portNumber ) { CFStringRef nameRef; MIDIEndpointRef portRef; - std::ostringstream ost; char name[128]; std::string stringName; + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); if ( portNumber >= MIDIGetNumberOfSources() ) { + std::ostringstream ost; ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - //RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return stringName; } portRef = MIDIGetSource( portNumber ); nameRef = ConnectedEndpointName(portRef); - CFStringGetCString( nameRef, name, sizeof(name), 0); + CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); CFRelease( nameRef ); return stringName = name; @@ -813,7 +855,8 @@ void MidiOutCore :: initialize( const std::string& clientName ) OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client ); if ( result != noErr ) { errorString_ = "MidiOutCore::initialize: error creating OS-X MIDI client object."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Save our api-specific connection information. @@ -825,6 +868,7 @@ void MidiOutCore :: initialize( const std::string& clientName ) unsigned int MidiOutCore :: getPortCount() { + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); return MIDIGetNumberOfDestinations(); } @@ -832,21 +876,21 @@ std::string MidiOutCore :: getPortName( unsigned int portNumber ) { CFStringRef nameRef; MIDIEndpointRef portRef; - std::ostringstream ost; char name[128]; std::string stringName; + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); if ( portNumber >= MIDIGetNumberOfDestinations() ) { + std::ostringstream ost; ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return stringName; - //RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); } portRef = MIDIGetDestination( portNumber ); nameRef = ConnectedEndpointName(portRef); - CFStringGetCString( nameRef, name, sizeof(name), 0); + CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); CFRelease( nameRef ); return stringName = name; @@ -856,21 +900,24 @@ void MidiOutCore :: openPort( unsigned int portNumber, const std::string portNam { if ( connected_ ) { errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nDest = MIDIGetNumberOfDestinations(); if (nDest < 1) { errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; - RtMidi::error( RtError::NO_DEVICES_FOUND, errorString_ ); + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; } - std::ostringstream ost; if ( portNumber >= nDest ) { + std::ostringstream ost; ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; } MIDIPortRef port; @@ -881,7 +928,8 @@ void MidiOutCore :: openPort( unsigned int portNumber, const std::string portNam if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Get the desired output port identifier. @@ -890,7 +938,8 @@ void MidiOutCore :: openPort( unsigned int portNumber, const std::string portNam MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Save our api-specific connection information. @@ -914,7 +963,7 @@ void MidiOutCore :: openVirtualPort( std::string portName ) if ( data->endpoint ) { errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -925,21 +974,20 @@ void MidiOutCore :: openVirtualPort( std::string portName ) &endpoint ); if ( result != noErr ) { errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Save our api-specific connection information. data->endpoint = endpoint; } -char *sysexBuffer = 0; - -void sysexCompletionProc( MIDISysexSendRequest * sreq ) -{ - //std::cout << "Completed SysEx send\n"; - delete sysexBuffer; - sysexBuffer = 0; -} +// Not necessary if we don't treat sysex messages any differently than +// normal messages ... see below. +//static void sysexCompletionProc( MIDISysexSendRequest *sreq ) +//{ +// free( sreq ); +//} void MidiOutCore :: sendMessage( std::vector *message ) { @@ -948,7 +996,7 @@ void MidiOutCore :: sendMessage( std::vector *message ) unsigned int nBytes = message->size(); if ( nBytes == 0 ) { errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -958,45 +1006,56 @@ void MidiOutCore :: sendMessage( std::vector *message ) CoreMidiData *data = static_cast (apiData_); OSStatus result; + /* + // I don't think this code is necessary. We can send sysex + // messages through the normal mechanism. In addition, this avoids + // the problem of virtual ports not receiving sysex messages. + if ( message->at(0) == 0xF0 ) { - while ( sysexBuffer != 0 ) usleep( 1000 ); // sleep 1 ms + // Apple's fantastic API requires us to free the allocated data in + // the completion callback but trashes the pointer and size before + // we get a chance to free it!! This is a somewhat ugly hack + // submitted by ptarabbia that puts the sysex buffer data right at + // the end of the MIDISysexSendRequest structure. This solution + // does not require that we wait for a previous sysex buffer to be + // sent before sending a new one, which was the old way we did it. + MIDISysexSendRequest *newRequest = (MIDISysexSendRequest *) malloc(sizeof(struct MIDISysexSendRequest) + nBytes); + char * sysexBuffer = ((char *) newRequest) + sizeof(struct MIDISysexSendRequest); - sysexBuffer = new char[nBytes]; - if ( sysexBuffer == NULL ) { - errorString_ = "MidiOutCore::sendMessage: error allocating sysex message memory!"; - RtMidi::error( RtError::MEMORY_ERROR, errorString_ ); - } + // Copy data to buffer. + for ( unsigned int i=0; iat(i); - // Copy data to buffer. - for ( unsigned int i=0; iat(i); + newRequest->destination = data->destinationId; + newRequest->data = (Byte *)sysexBuffer; + newRequest->bytesToSend = nBytes; + newRequest->complete = 0; + newRequest->completionProc = sysexCompletionProc; + newRequest->completionRefCon = newRequest; - data->sysexreq.destination = data->destinationId; - data->sysexreq.data = (Byte *)sysexBuffer; - data->sysexreq.bytesToSend = nBytes; - data->sysexreq.complete = 0; - data->sysexreq.completionProc = sysexCompletionProc; - data->sysexreq.completionRefCon = &(data->sysexreq); - - result = MIDISendSysex( &(data->sysexreq) ); - if ( result != noErr ) { - errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; - RtMidi::error( RtError::WARNING, errorString_ ); - } - return; + result = MIDISendSysex(newRequest); + if ( result != noErr ) { + free( newRequest ); + errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; + error( RtMidiError::WARNING, errorString_ ); + return; + } + return; } else if ( nBytes > 3 ) { - errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; - RtMidi::error( RtError::WARNING, errorString_ ); - return; + errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; + error( RtMidiError::WARNING, errorString_ ); + return; } + */ MIDIPacketList packetList; MIDIPacket *packet = MIDIPacketListInit( &packetList ); packet = MIDIPacketListAdd( &packetList, sizeof(packetList), packet, timeStamp, nBytes, (const Byte *) &message->at( 0 ) ); if ( !packet ) { errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Send to any destinations that may have connected to us. @@ -1004,7 +1063,7 @@ void MidiOutCore :: sendMessage( std::vector *message ) result = MIDIReceived( data->endpoint, &packetList ); if ( result != noErr ) { errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } } @@ -1013,10 +1072,9 @@ void MidiOutCore :: sendMessage( std::vector *message ) result = MIDISend( data->port, data->destinationId, &packetList ); if ( result != noErr ) { errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } } - } #endif // __MACOSX_CORE__ @@ -1047,17 +1105,6 @@ void MidiOutCore :: sendMessage( std::vector *message ) // ALSA header file. #include -// Global sequencer instance created when first In/Out object is -// created, then destroyed when last In/Out is deleted. -static snd_seq_t *s_seq = NULL; - -// Variable to keep track of how many ports are open. -static unsigned int s_numPorts = 0; - -// The client name to use when creating the sequencer, which is -// currently set on the first call to createSequencer. -static std::string s_clientName = "RtMidi Client"; - // A structure to hold variables related to the ALSA API // implementation. struct AlsaMidiData { @@ -1077,44 +1124,12 @@ struct AlsaMidiData { #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) -snd_seq_t* createSequencer( const std::string& clientName ) -{ - // Set up the ALSA sequencer client. - if ( s_seq == NULL ) { - int result = snd_seq_open(&s_seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); - if ( result < 0 ) { - s_seq = NULL; - } - else { - // Set client name, use current name if given string is empty. - if ( clientName != "" ) { - s_clientName = clientName; - } - snd_seq_set_client_name( s_seq, s_clientName.c_str() ); - } - } - - // Increment port count. - s_numPorts++; - - return s_seq; -} - -void freeSequencer ( void ) -{ - s_numPorts--; - if ( s_numPorts == 0 && s_seq != NULL ) { - snd_seq_close( s_seq ); - s_seq = NULL; - } -} - //*********************************************************************// // API: LINUX ALSA // Class Definitions: MidiInAlsa //*********************************************************************// -extern "C" void *alsaMidiHandler( void *ptr ) +static void *alsaMidiHandler( void *ptr ) { MidiInApi::RtMidiInData *data = static_cast (ptr); AlsaMidiData *apiData = static_cast (data->apiData); @@ -1174,7 +1189,8 @@ extern "C" void *alsaMidiHandler( void *ptr ) continue; } else if ( result <= 0 ) { - std::cerr << "MidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; + perror("System reports"); continue; } @@ -1185,13 +1201,13 @@ extern "C" void *alsaMidiHandler( void *ptr ) doDecode = false; switch ( ev->type ) { - case SND_SEQ_EVENT_PORT_SUBSCRIBED: + case SND_SEQ_EVENT_PORT_SUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; #endif break; - case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" @@ -1206,7 +1222,11 @@ extern "C" void *alsaMidiHandler( void *ptr ) if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; - case SND_SEQ_EVENT_TICK: // MIDI timing tick + case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick + if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; + break; + + case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; @@ -1328,19 +1348,24 @@ MidiInAlsa :: ~MidiInAlsa() #ifndef AVOID_TIMESTAMPING snd_seq_free_queue( data->seq, data->queue_id ); #endif - freeSequencer(); + snd_seq_close( data->seq ); delete data; } void MidiInAlsa :: initialize( const std::string& clientName ) { - snd_seq_t* seq = createSequencer( clientName ); - if ( seq == NULL ) { - s_seq = NULL; + // Set up the ALSA sequencer client. + snd_seq_t *seq; + int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); + if ( result < 0 ) { errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } + // Set client name. + snd_seq_set_client_name( seq, clientName.c_str() ); + // Save our api-specific connection information. AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; data->seq = seq; @@ -1356,12 +1381,13 @@ void MidiInAlsa :: initialize( const std::string& clientName ) if ( pipe(data->trigger_fds) == -1 ) { errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Create the input queue #ifndef AVOID_TIMESTAMPING - data->queue_id = snd_seq_alloc_named_queue(s_seq, "RtMidi Queue"); + data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); // Set arbitrary tempo (mm=100) and resolution (240) snd_seq_queue_tempo_t *qtempo; snd_seq_queue_tempo_alloca(&qtempo); @@ -1425,8 +1451,8 @@ std::string MidiInAlsa :: getPortName( unsigned int portNumber ) snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; os << snd_seq_client_info_get_name( cinfo ); - os << " "; // GO: These lines added to make sure devices are listed - os << snd_seq_port_info_get_client( pinfo ); // GO: with full portnames added to ensure individual device names + os << " "; // These lines added to make sure devices are listed + os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; os << snd_seq_port_info_get_port( pinfo ); stringName = os.str(); @@ -1435,40 +1461,42 @@ std::string MidiInAlsa :: getPortName( unsigned int portNumber ) // If we get here, we didn't find a match. errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return stringName; - //RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); } void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName ) { if ( connected_ ) { errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nSrc = this->getPortCount(); - if (nSrc < 1) { + if ( nSrc < 1 ) { errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; - RtMidi::error( RtError::NO_DEVICES_FOUND, errorString_ ); + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; } + snd_seq_port_info_t *src_pinfo; + snd_seq_port_info_alloca( &src_pinfo ); + AlsaMidiData *data = static_cast (apiData_); + if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { + std::ostringstream ost; + ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + snd_seq_addr_t sender, receiver; + sender.client = snd_seq_port_info_get_client( src_pinfo ); + sender.port = snd_seq_port_info_get_port( src_pinfo ); + snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); - std::ostringstream ost; - AlsaMidiData *data = static_cast (apiData_); - if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { - ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); - } - - - snd_seq_addr_t sender, receiver; - sender.client = snd_seq_port_info_get_client( pinfo ); - sender.port = snd_seq_port_info_get_port( pinfo ); - receiver.client = snd_seq_client_id( data->seq ); if ( data->vport < 0 ) { snd_seq_port_info_set_client( pinfo, 0 ); snd_seq_port_info_set_port( pinfo, 0 ); @@ -1489,17 +1517,21 @@ void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } + data->vport = snd_seq_port_info_get_port(pinfo); } + receiver.client = snd_seq_port_info_get_client( pinfo ); receiver.port = data->vport; if ( !data->subscription ) { // Make subscription if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } snd_seq_port_subscribe_set_sender(data->subscription, &sender); snd_seq_port_subscribe_set_dest(data->subscription, &receiver); @@ -1507,7 +1539,8 @@ void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } } @@ -1532,7 +1565,8 @@ void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName data->subscription = 0; inputData_.doInput = false; errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; - RtMidi::error( RtError::THREAD_ERROR, errorString_ ); + error( RtMidiError::THREAD_ERROR, errorString_ ); + return; } } @@ -1562,8 +1596,10 @@ void MidiInAlsa :: openVirtualPort( std::string portName ) if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } + data->vport = snd_seq_port_info_get_port(pinfo); } if ( inputData_.doInput == false ) { @@ -1593,7 +1629,8 @@ void MidiInAlsa :: openVirtualPort( std::string portName ) } inputData_.doInput = false; errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; - RtMidi::error( RtError::THREAD_ERROR, errorString_ ); + error( RtMidiError::THREAD_ERROR, errorString_ ); + return; } } } @@ -1646,19 +1683,24 @@ MidiOutAlsa :: ~MidiOutAlsa() if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); if ( data->coder ) snd_midi_event_free( data->coder ); if ( data->buffer ) free( data->buffer ); - freeSequencer(); + snd_seq_close( data->seq ); delete data; } void MidiOutAlsa :: initialize( const std::string& clientName ) { - snd_seq_t* seq = createSequencer( clientName ); - if ( seq == NULL ) { - s_seq = NULL; + // Set up the ALSA sequencer client. + snd_seq_t *seq; + int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); + if ( result1 < 0 ) { errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } + // Set client name. + snd_seq_set_client_name( seq, clientName.c_str() ); + // Save our api-specific connection information. AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; data->seq = seq; @@ -1671,13 +1713,15 @@ void MidiOutAlsa :: initialize( const std::string& clientName ) if ( result < 0 ) { delete data; errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { delete data; errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; - RtMidi::error( RtError::MEMORY_ERROR, errorString_ ); + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; } snd_midi_event_init( data->coder ); apiData_ = (void *) data; @@ -1706,6 +1750,8 @@ std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; os << snd_seq_client_info_get_name(cinfo); + os << " "; // These lines added to make sure devices are listed + os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; os << snd_seq_port_info_get_port(pinfo); stringName = os.str(); @@ -1714,8 +1760,7 @@ std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) // If we get here, we didn't find a match. errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; - //RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return stringName; } @@ -1723,24 +1768,26 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portNam { if ( connected_ ) { errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nSrc = this->getPortCount(); if (nSrc < 1) { errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; - RtMidi::error( RtError::NO_DEVICES_FOUND, errorString_ ); + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; } snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); - std::ostringstream ost; AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { + std::ostringstream ost; ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; } snd_seq_addr_t sender, receiver; @@ -1754,7 +1801,8 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portNam SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); if ( data->vport < 0 ) { errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } } @@ -1763,8 +1811,9 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portNam // Make subscription if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { snd_seq_port_subscribe_free( data->subscription ); - errorString_ = "MidiOutAlsa::openPort: error allocation port subscribtion."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } snd_seq_port_subscribe_set_sender(data->subscription, &sender); snd_seq_port_subscribe_set_dest(data->subscription, &receiver); @@ -1773,7 +1822,8 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portNam if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } connected_ = true; @@ -1799,7 +1849,7 @@ void MidiOutAlsa :: openVirtualPort( std::string portName ) if ( data->vport < 0 ) { errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); } } } @@ -1814,13 +1864,15 @@ void MidiOutAlsa :: sendMessage( std::vector *message ) result = snd_midi_event_resize_buffer ( data->coder, nBytes); if ( result != 0 ) { errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } free (data->buffer); data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; - RtMidi::error( RtError::MEMORY_ERROR, errorString_ ); + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; } } @@ -1833,7 +1885,7 @@ void MidiOutAlsa :: sendMessage( std::vector *message ) result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); if ( result < (int)nBytes ) { errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -1841,7 +1893,8 @@ void MidiOutAlsa :: sendMessage( std::vector *message ) result = snd_seq_event_output(data->seq, &ev); if ( result < 0 ) { errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); + return; } snd_seq_drain_output(data->seq); } @@ -1879,6 +1932,7 @@ struct WinMidiData { DWORD lastTime; MidiInApi::MidiMessage message; LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; + CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo }; //*********************************************************************// @@ -1886,7 +1940,7 @@ struct WinMidiData { // Class Definitions: MidiInWinMM //*********************************************************************// -static void CALLBACK midiInputCallback( HMIDIIN hmin, +static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage, @@ -1952,9 +2006,11 @@ static void CALLBACK midiInputCallback( HMIDIIN hmin, // buffer when an application closes and in this case, we should // avoid requeueing it, else the computer suddenly reboots after // one or two minutes. - if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { - //if ( sysex->dwBytesRecorded > 0 ) { + if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { + //if ( sysex->dwBytesRecorded > 0 ) { + EnterCriticalSection( &(apiData->_mutex) ); MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); + LeaveCriticalSection( &(apiData->_mutex) ); if ( result != MMSYSERR_NOERROR ) std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; @@ -1993,8 +2049,10 @@ MidiInWinMM :: ~MidiInWinMM() // Close a connection if it exists. closePort(); - // Cleanup. WinMidiData *data = static_cast (apiData_); + DeleteCriticalSection( &(data->_mutex) ); + + // Cleanup. delete data; } @@ -2005,7 +2063,7 @@ void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) unsigned int nDevices = midiInGetNumDevs(); if ( nDevices == 0 ) { errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } // Save our api-specific connection information. @@ -2013,27 +2071,34 @@ void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) apiData_ = (void *) data; inputData_.apiData = (void *) data; data->message.bytes.clear(); // needs to be empty for first input message + + if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) { + errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; + error( RtMidiError::WARNING, errorString_ ); + } } void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) { if ( connected_ ) { errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nDevices = midiInGetNumDevs(); if (nDevices == 0) { errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; - RtMidi::error( RtError::NO_DEVICES_FOUND, errorString_ ); + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; } - std::ostringstream ost; if ( portNumber >= nDevices ) { + std::ostringstream ost; ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; } WinMidiData *data = static_cast (apiData_); @@ -2044,7 +2109,8 @@ void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portN CALLBACK_FUNCTION ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Allocate and init the sysex buffers. @@ -2059,7 +2125,8 @@ void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portN if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Register the buffer. @@ -2067,7 +2134,8 @@ void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portN if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } } @@ -2075,23 +2143,25 @@ void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portN if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } connected_ = true; } -void MidiInWinMM :: openVirtualPort( std::string portName ) +void MidiInWinMM :: openVirtualPort( std::string /*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } void MidiInWinMM :: closePort( void ) { if ( connected_ ) { WinMidiData *data = static_cast (apiData_); + EnterCriticalSection( &(data->_mutex) ); midiInReset( data->inHandle ); midiInStop( data->inHandle ); @@ -2102,12 +2172,14 @@ void MidiInWinMM :: closePort( void ) if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } } midiInClose( data->inHandle ); connected_ = false; + LeaveCriticalSection( &(data->_mutex) ); } } @@ -2124,8 +2196,7 @@ std::string MidiInWinMM :: getPortName( unsigned int portNumber ) std::ostringstream ost; ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - //RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return stringName; } @@ -2133,9 +2204,9 @@ std::string MidiInWinMM :: getPortName( unsigned int portNumber ) midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); #if defined( UNICODE ) || defined( _UNICODE ) - int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL); + int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; stringName.assign( length, 0 ); - length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, wcslen(deviceCaps.szPname), &stringName[0], length, NULL, NULL); + length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); #else stringName = std::string( deviceCaps.szPname ); #endif @@ -2178,7 +2249,7 @@ void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) unsigned int nDevices = midiOutGetNumDevs(); if ( nDevices == 0 ) { errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } // Save our api-specific connection information. @@ -2199,8 +2270,7 @@ std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) std::ostringstream ost; ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - //RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return stringName; } @@ -2208,9 +2278,9 @@ std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); #if defined( UNICODE ) || defined( _UNICODE ) - int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL); + int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; stringName.assign( length, 0 ); - length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, wcslen(deviceCaps.szPname), &stringName[0], length, NULL, NULL); + length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); #else stringName = std::string( deviceCaps.szPname ); #endif @@ -2222,21 +2292,23 @@ void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*port { if ( connected_ ) { errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nDevices = midiOutGetNumDevs(); if (nDevices < 1) { errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; - RtMidi::error( RtError::NO_DEVICES_FOUND, errorString_ ); + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; } - std::ostringstream ost; if ( portNumber >= nDevices ) { + std::ostringstream ost; ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::INVALID_PARAMETER, errorString_ ); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; } WinMidiData *data = static_cast (apiData_); @@ -2247,7 +2319,8 @@ void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*port CALLBACK_NULL ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } connected_ = true; @@ -2263,19 +2336,21 @@ void MidiOutWinMM :: closePort( void ) } } -void MidiOutWinMM :: openVirtualPort( std::string portName ) +void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } void MidiOutWinMM :: sendMessage( std::vector *message ) { + if ( !connected_ ) return; + unsigned int nBytes = static_cast(message->size()); if ( nBytes == 0 ) { errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -2287,7 +2362,8 @@ void MidiOutWinMM :: sendMessage( std::vector *message ) char *buffer = (char *) malloc( nBytes ); if ( buffer == NULL ) { errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; - RtMidi::error( RtError::MEMORY_ERROR, errorString_ ); + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; } // Copy data to buffer. @@ -2302,7 +2378,8 @@ void MidiOutWinMM :: sendMessage( std::vector *message ) if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Send the message. @@ -2310,20 +2387,20 @@ void MidiOutWinMM :: sendMessage( std::vector *message ) if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Unprepare the buffer and MIDIHDR. while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); free( buffer ); - } else { // Channel or system message. // Make sure the message size isn't too big. if ( nBytes > 3 ) { errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -2339,1039 +2416,13 @@ void MidiOutWinMM :: sendMessage( std::vector *message ) result = midiOutShortMsg( data->outHandle, packet ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); } } } #endif // __WINDOWS_MM__ -// *********************************************************************// -// API: WINDOWS Kernel Streaming -// -// Written by Sebastien Alaiwan, 2012. -// -// NOTE BY GARY: much of the KS-specific code below probably should go in a separate file. -// -// *********************************************************************// - -#if defined(__WINDOWS_KS__) - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ks.h" -#include "ksmedia.h" - -#define INSTANTIATE_GUID(a) GUID const a = { STATIC_ ## a } - -INSTANTIATE_GUID(GUID_NULL); -INSTANTIATE_GUID(KSPROPSETID_Pin); -INSTANTIATE_GUID(KSPROPSETID_Connection); -INSTANTIATE_GUID(KSPROPSETID_Topology); -INSTANTIATE_GUID(KSINTERFACESETID_Standard); -INSTANTIATE_GUID(KSMEDIUMSETID_Standard); -INSTANTIATE_GUID(KSDATAFORMAT_TYPE_MUSIC); -INSTANTIATE_GUID(KSDATAFORMAT_SUBTYPE_MIDI); -INSTANTIATE_GUID(KSDATAFORMAT_SPECIFIER_NONE); - -#undef INSTANTIATE_GUID - -typedef std::basic_string tstring; - -inline bool IsValid(HANDLE handle) -{ - return handle != NULL && handle != INVALID_HANDLE_VALUE; -} - -class ComException : public std::runtime_error -{ -private: - static std::string MakeString(std::string const& s, HRESULT hr) - { - std::stringstream ss; - ss << "(error 0x" << std::hex << hr << ")"; - return s + ss.str(); - } - -public: - ComException(std::string const& s, HRESULT hr) : - std::runtime_error(MakeString(s, hr)) - { - } -}; - -template -class CKsEnumFilters -{ -public: - ~CKsEnumFilters() - { - DestroyLists(); - } - - void EnumFilters(GUID const* categories, size_t numCategories) - { - DestroyLists(); - - if (categories == 0) - throw std::runtime_error("CKsEnumFilters: invalid argument"); - - // Get a handle to the device set specified by the guid - HDEVINFO hDevInfo = ::SetupDiGetClassDevs(&categories[0], NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (!IsValid(hDevInfo)) - throw std::runtime_error("CKsEnumFilters: no devices found"); - - // Loop through members of the set and get details for each - for (int iClassMember=0;;iClassMember++) { - try { - SP_DEVICE_INTERFACE_DATA DID; - DID.cbSize = sizeof(DID); - DID.Reserved = 0; - - bool fRes = ::SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &categories[0], iClassMember, &DID); - if (!fRes) - break; - - // Get filter friendly name - HKEY hRegKey = ::SetupDiOpenDeviceInterfaceRegKey(hDevInfo, &DID, 0, KEY_READ); - if (hRegKey == INVALID_HANDLE_VALUE) - throw std::runtime_error("CKsEnumFilters: interface has no registry"); - - char friendlyName[256]; - DWORD dwSize = sizeof friendlyName; - LONG lval = ::RegQueryValueEx(hRegKey, TEXT("FriendlyName"), NULL, NULL, (LPBYTE)friendlyName, &dwSize); - ::RegCloseKey(hRegKey); - if (lval != ERROR_SUCCESS) - throw std::runtime_error("CKsEnumFilters: interface has no friendly name"); - - // Get details for the device registered in this class - DWORD const cbItfDetails = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + MAX_PATH * sizeof(WCHAR); - std::vector buffer(cbItfDetails); - - SP_DEVICE_INTERFACE_DETAIL_DATA* pDevInterfaceDetails = reinterpret_cast(&buffer[0]); - pDevInterfaceDetails->cbSize = sizeof(*pDevInterfaceDetails); - - SP_DEVINFO_DATA DevInfoData; - DevInfoData.cbSize = sizeof(DevInfoData); - DevInfoData.Reserved = 0; - - fRes = ::SetupDiGetDeviceInterfaceDetail(hDevInfo, &DID, pDevInterfaceDetails, cbItfDetails, NULL, &DevInfoData); - if (!fRes) - throw std::runtime_error("CKsEnumFilters: could not get interface details"); - - // check additional category guids which may (or may not) have been supplied - for (size_t i=1; i < numCategories; ++i) { - SP_DEVICE_INTERFACE_DATA DIDAlias; - DIDAlias.cbSize = sizeof(DIDAlias); - DIDAlias.Reserved = 0; - - fRes = ::SetupDiGetDeviceInterfaceAlias(hDevInfo, &DID, &categories[i], &DIDAlias); - if (!fRes) - throw std::runtime_error("CKsEnumFilters: could not get interface alias"); - - // Check if the this interface alias is enabled. - if (!DIDAlias.Flags || (DIDAlias.Flags & SPINT_REMOVED)) - throw std::runtime_error("CKsEnumFilters: interface alias is not enabled"); - } - - std::auto_ptr pFilter(new TFilterType(pDevInterfaceDetails->DevicePath, friendlyName)); - - pFilter->Instantiate(); - pFilter->FindMidiPins(); - pFilter->Validate(); - - m_Filters.push_back(pFilter.release()); - } - catch (std::runtime_error const& e) { - } - } - - ::SetupDiDestroyDeviceInfoList(hDevInfo); - } - -private: - void DestroyLists() - { - for (size_t i=0;i < m_Filters.size();++i) - delete m_Filters[i]; - m_Filters.clear(); - } - -public: - // TODO: make this private. - std::vector m_Filters; -}; - -class CKsObject -{ -public: - CKsObject(HANDLE handle) : m_handle(handle) - { - } - -protected: - HANDLE m_handle; - - void SetProperty(REFGUID guidPropertySet, ULONG nProperty, void* pvValue, ULONG cbValue) - { - KSPROPERTY ksProperty; - memset(&ksProperty, 0, sizeof ksProperty); - ksProperty.Set = guidPropertySet; - ksProperty.Id = nProperty; - ksProperty.Flags = KSPROPERTY_TYPE_SET; - - HRESULT hr = DeviceIoControlKsProperty(ksProperty, pvValue, cbValue); - if (FAILED(hr)) - throw ComException("CKsObject::SetProperty: could not set property", hr); - } - -private: - - HRESULT DeviceIoControlKsProperty(KSPROPERTY& ksProperty, void* pvValue, ULONG cbValue) - { - ULONG ulReturned; - return ::DeviceIoControl( - m_handle, - IOCTL_KS_PROPERTY, - &ksProperty, - sizeof(ksProperty), - pvValue, - cbValue, - &ulReturned, - NULL); - } -}; - -class CKsPin; - -class CKsFilter : public CKsObject -{ - friend class CKsPin; - -public: - CKsFilter(tstring const& name, std::string const& sFriendlyName); - virtual ~CKsFilter(); - - virtual void Instantiate(); - - template - T GetPinProperty(ULONG nPinId, ULONG nProperty) - { - ULONG ulReturned = 0; - T value; - - KSP_PIN ksPProp; - ksPProp.Property.Set = KSPROPSETID_Pin; - ksPProp.Property.Id = nProperty; - ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; - ksPProp.PinId = nPinId; - ksPProp.Reserved = 0; - - HRESULT hr = ::DeviceIoControl( - m_handle, - IOCTL_KS_PROPERTY, - &ksPProp, - sizeof(KSP_PIN), - &value, - sizeof(value), - &ulReturned, - NULL); - if (FAILED(hr)) - throw ComException("CKsFilter::GetPinProperty: failed to retrieve property", hr); - - return value; - } - - void GetPinPropertyMulti(ULONG nPinId, REFGUID guidPropertySet, ULONG nProperty, PKSMULTIPLE_ITEM* ppKsMultipleItem) - { - HRESULT hr; - - KSP_PIN ksPProp; - ksPProp.Property.Set = guidPropertySet; - ksPProp.Property.Id = nProperty; - ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; - ksPProp.PinId = nPinId; - ksPProp.Reserved = 0; - - ULONG cbMultipleItem = 0; - hr = ::DeviceIoControl(m_handle, - IOCTL_KS_PROPERTY, - &ksPProp.Property, - sizeof(KSP_PIN), - NULL, - 0, - &cbMultipleItem, - NULL); - if (FAILED(hr)) - throw ComException("CKsFilter::GetPinPropertyMulti: cannot get property", hr); - - *ppKsMultipleItem = (PKSMULTIPLE_ITEM) new BYTE[cbMultipleItem]; - - ULONG ulReturned = 0; - hr = ::DeviceIoControl( - m_handle, - IOCTL_KS_PROPERTY, - &ksPProp, - sizeof(KSP_PIN), - (PVOID)*ppKsMultipleItem, - cbMultipleItem, - &ulReturned, - NULL); - if (FAILED(hr)) - throw ComException("CKsFilter::GetPinPropertyMulti: cannot get property", hr); - } - - std::string const& GetFriendlyName() const - { - return m_sFriendlyName; - } - -protected: - - std::vector m_Pins; // this list owns the pins. - - std::vector m_RenderPins; - std::vector m_CapturePins; - -private: - std::string const m_sFriendlyName; // friendly name eg "Virus TI Synth" - tstring const m_sName; // Filter path, eg "\\?\usb#vid_133e&pid_0815...\vtimidi02" -}; - -class CKsPin : public CKsObject -{ -public: - CKsPin(CKsFilter* pFilter, ULONG nId); - virtual ~CKsPin(); - - virtual void Instantiate(); - - void ClosePin(); - - void SetState(KSSTATE ksState); - - void WriteData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED); - void ReadData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED); - - KSPIN_DATAFLOW GetDataFlow() const - { - return m_DataFlow; - } - - bool IsSink() const - { - return m_Communication == KSPIN_COMMUNICATION_SINK - || m_Communication == KSPIN_COMMUNICATION_BOTH; - } - - -protected: - PKSPIN_CONNECT m_pKsPinConnect; // creation parameters of pin - CKsFilter* const m_pFilter; - - ULONG m_cInterfaces; - PKSIDENTIFIER m_pInterfaces; - PKSMULTIPLE_ITEM m_pmiInterfaces; - - ULONG m_cMediums; - PKSIDENTIFIER m_pMediums; - PKSMULTIPLE_ITEM m_pmiMediums; - - ULONG m_cDataRanges; - PKSDATARANGE m_pDataRanges; - PKSMULTIPLE_ITEM m_pmiDataRanges; - - KSPIN_DATAFLOW m_DataFlow; - KSPIN_COMMUNICATION m_Communication; -}; - -CKsFilter::CKsFilter(tstring const& sName, std::string const& sFriendlyName) : - CKsObject(INVALID_HANDLE_VALUE), - m_sFriendlyName(sFriendlyName), - m_sName(sName) -{ - if (sName.empty()) - throw std::runtime_error("CKsFilter::CKsFilter: name can't be empty"); -} - -CKsFilter::~CKsFilter() -{ - for (size_t i=0;i < m_Pins.size();++i) - delete m_Pins[i]; - - if (IsValid(m_handle)) - ::CloseHandle(m_handle); -} - -void CKsFilter::Instantiate() -{ - m_handle = CreateFile( - m_sName.c_str(), - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL); - - if (!IsValid(m_handle)) - { - DWORD const dwError = GetLastError(); - throw ComException("CKsFilter::Instantiate: can't open driver", HRESULT_FROM_WIN32(dwError)); - } -} - -CKsPin::CKsPin(CKsFilter* pFilter, ULONG PinId) : - CKsObject(INVALID_HANDLE_VALUE), - m_pKsPinConnect(NULL), - m_pFilter(pFilter) -{ - m_Communication = m_pFilter->GetPinProperty(PinId, KSPROPERTY_PIN_COMMUNICATION); - m_DataFlow = m_pFilter->GetPinProperty(PinId, KSPROPERTY_PIN_DATAFLOW); - - // Interfaces - m_pFilter->GetPinPropertyMulti( - PinId, - KSPROPSETID_Pin, - KSPROPERTY_PIN_INTERFACES, - &m_pmiInterfaces); - - m_cInterfaces = m_pmiInterfaces->Count; - m_pInterfaces = (PKSPIN_INTERFACE)(m_pmiInterfaces + 1); - - // Mediums - m_pFilter->GetPinPropertyMulti( - PinId, - KSPROPSETID_Pin, - KSPROPERTY_PIN_MEDIUMS, - &m_pmiMediums); - - m_cMediums = m_pmiMediums->Count; - m_pMediums = (PKSPIN_MEDIUM)(m_pmiMediums + 1); - - // Data ranges - m_pFilter->GetPinPropertyMulti( - PinId, - KSPROPSETID_Pin, - KSPROPERTY_PIN_DATARANGES, - &m_pmiDataRanges); - - m_cDataRanges = m_pmiDataRanges->Count; - m_pDataRanges = (PKSDATARANGE)(m_pmiDataRanges + 1); -} - -CKsPin::~CKsPin() -{ - ClosePin(); - - delete[] (BYTE*)m_pKsPinConnect; - delete[] (BYTE*)m_pmiDataRanges; - delete[] (BYTE*)m_pmiInterfaces; - delete[] (BYTE*)m_pmiMediums; -} - -void CKsPin::ClosePin() -{ - if (IsValid(m_handle)) { - SetState(KSSTATE_STOP); - ::CloseHandle(m_handle); - } - m_handle = INVALID_HANDLE_VALUE; -} - -void CKsPin::SetState(KSSTATE ksState) -{ - SetProperty(KSPROPSETID_Connection, KSPROPERTY_CONNECTION_STATE, &ksState, sizeof(ksState)); -} - -void CKsPin::Instantiate() -{ - if (!m_pKsPinConnect) - throw std::runtime_error("CKsPin::Instanciate: abstract pin"); - - DWORD const dwResult = KsCreatePin(m_pFilter->m_handle, m_pKsPinConnect, GENERIC_WRITE | GENERIC_READ, &m_handle); - if (dwResult != ERROR_SUCCESS) - throw ComException("CKsMidiCapFilter::CreateRenderPin: Pin instanciation failed", HRESULT_FROM_WIN32(dwResult)); -} - -void CKsPin::WriteData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED) -{ - DWORD cbWritten; - BOOL fRes = ::DeviceIoControl( - m_handle, - IOCTL_KS_WRITE_STREAM, - NULL, - 0, - pKSSTREAM_HEADER, - pKSSTREAM_HEADER->Size, - &cbWritten, - pOVERLAPPED); - if (!fRes) { - DWORD const dwError = GetLastError(); - if (dwError != ERROR_IO_PENDING) - throw ComException("CKsPin::WriteData: DeviceIoControl failed", HRESULT_FROM_WIN32(dwError)); - } -} - -void CKsPin::ReadData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED) -{ - DWORD cbReturned; - BOOL fRes = ::DeviceIoControl( - m_handle, - IOCTL_KS_READ_STREAM, - NULL, - 0, - pKSSTREAM_HEADER, - pKSSTREAM_HEADER->Size, - &cbReturned, - pOVERLAPPED); - if (!fRes) { - DWORD const dwError = GetLastError(); - if (dwError != ERROR_IO_PENDING) - throw ComException("CKsPin::ReadData: DeviceIoControl failed", HRESULT_FROM_WIN32(dwError)); - } -} - -class CKsMidiFilter : public CKsFilter -{ -public: - void FindMidiPins(); - -protected: - CKsMidiFilter(tstring const& sPath, std::string const& sFriendlyName); -}; - -class CKsMidiPin : public CKsPin -{ -public: - CKsMidiPin(CKsFilter* pFilter, ULONG nId); -}; - -class CKsMidiRenFilter : public CKsMidiFilter -{ -public: - CKsMidiRenFilter(tstring const& sPath, std::string const& sFriendlyName); - CKsMidiPin* CreateRenderPin(); - - void Validate() - { - if (m_RenderPins.empty()) - throw std::runtime_error("Could not find a MIDI render pin"); - } -}; - -class CKsMidiCapFilter : public CKsMidiFilter -{ -public: - CKsMidiCapFilter(tstring const& sPath, std::string const& sFriendlyName); - CKsMidiPin* CreateCapturePin(); - - void Validate() - { - if (m_CapturePins.empty()) - throw std::runtime_error("Could not find a MIDI capture pin"); - } -}; - -CKsMidiFilter::CKsMidiFilter(tstring const& sPath, std::string const& sFriendlyName) : - CKsFilter(sPath, sFriendlyName) -{ -} - -void CKsMidiFilter::FindMidiPins() -{ - ULONG numPins = GetPinProperty(0, KSPROPERTY_PIN_CTYPES); - - for (ULONG iPin = 0; iPin < numPins; ++iPin) { - try { - KSPIN_COMMUNICATION com = GetPinProperty(iPin, KSPROPERTY_PIN_COMMUNICATION); - if (com != KSPIN_COMMUNICATION_SINK && com != KSPIN_COMMUNICATION_BOTH) - throw std::runtime_error("Unknown pin communication value"); - - m_Pins.push_back(new CKsMidiPin(this, iPin)); - } - catch (std::runtime_error const&) { - // pin instanciation has failed, continue to the next pin. - } - } - - m_RenderPins.clear(); - m_CapturePins.clear(); - - for (size_t i = 0; i < m_Pins.size(); ++i) { - CKsPin* const pPin = m_Pins[i]; - - if (pPin->IsSink()) { - if (pPin->GetDataFlow() == KSPIN_DATAFLOW_IN) - m_RenderPins.push_back(pPin); - else - m_CapturePins.push_back(pPin); - } - } - - if (m_RenderPins.empty() && m_CapturePins.empty()) - throw std::runtime_error("No valid pins found on the filter."); -} - -CKsMidiRenFilter::CKsMidiRenFilter(tstring const& sPath, std::string const& sFriendlyName) : - CKsMidiFilter(sPath, sFriendlyName) -{ -} - -CKsMidiPin* CKsMidiRenFilter::CreateRenderPin() -{ - if (m_RenderPins.empty()) - throw std::runtime_error("Could not find a MIDI render pin"); - - CKsMidiPin* pPin = (CKsMidiPin*)m_RenderPins[0]; - pPin->Instantiate(); - return pPin; -} - -CKsMidiCapFilter::CKsMidiCapFilter(tstring const& sPath, std::string const& sFriendlyName) : - CKsMidiFilter(sPath, sFriendlyName) -{ -} - -CKsMidiPin* CKsMidiCapFilter::CreateCapturePin() -{ - if (m_CapturePins.empty()) - throw std::runtime_error("Could not find a MIDI capture pin"); - - CKsMidiPin* pPin = (CKsMidiPin*)m_CapturePins[0]; - pPin->Instantiate(); - return pPin; -} - -CKsMidiPin::CKsMidiPin(CKsFilter* pFilter, ULONG nId) : - CKsPin(pFilter, nId) -{ - DWORD const cbPinCreateSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT); - m_pKsPinConnect = (PKSPIN_CONNECT) new BYTE[cbPinCreateSize]; - - m_pKsPinConnect->Interface.Set = KSINTERFACESETID_Standard; - m_pKsPinConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING; - m_pKsPinConnect->Interface.Flags = 0; - m_pKsPinConnect->Medium.Set = KSMEDIUMSETID_Standard; - m_pKsPinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE; - m_pKsPinConnect->Medium.Flags = 0; - m_pKsPinConnect->PinId = nId; - m_pKsPinConnect->PinToHandle = NULL; - m_pKsPinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; - m_pKsPinConnect->Priority.PrioritySubClass = 1; - - // point m_pDataFormat to just after the pConnect struct - KSDATAFORMAT* m_pDataFormat = (KSDATAFORMAT*)(m_pKsPinConnect + 1); - m_pDataFormat->FormatSize = sizeof(KSDATAFORMAT); - m_pDataFormat->Flags = 0; - m_pDataFormat->SampleSize = 0; - m_pDataFormat->Reserved = 0; - m_pDataFormat->MajorFormat = GUID(KSDATAFORMAT_TYPE_MUSIC); - m_pDataFormat->SubFormat = GUID(KSDATAFORMAT_SUBTYPE_MIDI); - m_pDataFormat->Specifier = GUID(KSDATAFORMAT_SPECIFIER_NONE); - - bool hasStdStreamingInterface = false; - bool hasStdStreamingMedium = false; - - for ( ULONG i = 0; i < m_cInterfaces; i++ ) { - if (m_pInterfaces[i].Set == KSINTERFACESETID_Standard - && m_pInterfaces[i].Id == KSINTERFACE_STANDARD_STREAMING) - hasStdStreamingInterface = true; - } - - for (ULONG i = 0; i < m_cMediums; i++) { - if (m_pMediums[i].Set == KSMEDIUMSETID_Standard - && m_pMediums[i].Id == KSMEDIUM_STANDARD_DEVIO) - hasStdStreamingMedium = true; - } - - if (!hasStdStreamingInterface) // No standard streaming interfaces on the pin - throw std::runtime_error("CKsMidiPin::CKsMidiPin: no standard streaming interface"); - - if (!hasStdStreamingMedium) // No standard streaming mediums on the pin - throw std::runtime_error("CKsMidiPin::CKsMidiPin: no standard streaming medium"); - - bool hasMidiDataRange = false; - - BYTE const* pDataRangePtr = reinterpret_cast(m_pDataRanges); - - for (ULONG i = 0; i < m_cDataRanges; ++i) { - KSDATARANGE const* pDataRange = reinterpret_cast(pDataRangePtr); - - if (pDataRange->SubFormat == KSDATAFORMAT_SUBTYPE_MIDI) { - hasMidiDataRange = true; - break; - } - - pDataRangePtr += pDataRange->FormatSize; - } - - if (!hasMidiDataRange) // No MIDI dataranges on the pin - throw std::runtime_error("CKsMidiPin::CKsMidiPin: no MIDI datarange"); -} - - -struct WindowsKsData -{ - WindowsKsData() : m_pPin(NULL), m_Buffer(1024), m_hInputThread(NULL) - { - memset(&overlapped, 0, sizeof(OVERLAPPED)); - m_hExitEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); - overlapped.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); - m_hInputThread = NULL; - } - - ~WindowsKsData() - { - ::CloseHandle(overlapped.hEvent); - ::CloseHandle(m_hExitEvent); - } - - OVERLAPPED overlapped; - CKsPin* m_pPin; - std::vector m_Buffer; - std::auto_ptr > m_pCaptureEnum; - std::auto_ptr > m_pRenderEnum; - HANDLE m_hInputThread; - HANDLE m_hExitEvent; -}; - -// *********************************************************************// -// API: WINDOWS Kernel Streaming -// Class Definitions: MidiInWinKS -// *********************************************************************// - -DWORD WINAPI midiKsInputThread(VOID* pUser) -{ - MidiInApi::RtMidiInData* data = static_cast(pUser); - WindowsKsData* apiData = static_cast(data->apiData); - - HANDLE hEvents[] = { apiData->overlapped.hEvent, apiData->m_hExitEvent }; - - while ( true ) { - KSSTREAM_HEADER packet; - memset(&packet, 0, sizeof packet); - packet.Size = sizeof(KSSTREAM_HEADER); - packet.PresentationTime.Time = 0; - packet.PresentationTime.Numerator = 1; - packet.PresentationTime.Denominator = 1; - packet.Data = &apiData->m_Buffer[0]; - packet.DataUsed = 0; - packet.FrameExtent = apiData->m_Buffer.size(); - apiData->m_pPin->ReadData(&packet, &apiData->overlapped); - - DWORD dwRet = ::WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); - - if ( dwRet == WAIT_OBJECT_0 ) { - // parse packet - unsigned char* pData = (unsigned char*)packet.Data; - unsigned int iOffset = 0; - - while ( iOffset < packet.DataUsed ) { - KSMUSICFORMAT* pMusic = (KSMUSICFORMAT*)&pData[iOffset]; - iOffset += sizeof(KSMUSICFORMAT); - - MidiInApi::MidiMessage message; - message.timeStamp = 0; - for(size_t i=0;i < pMusic->ByteCount;++i) - message.bytes.push_back(pData[iOffset+i]); - - if ( data->usingCallback ) { - RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback)data->userCallback; - callback(message.timeStamp, &message.bytes, data->userData); - } - else { - // As long as we haven't reached our queue size limit, push the message. - if ( data->queue.size < data->queue.ringSize ) { - data->queue.ring[data->queue.back++] = message; - if(data->queue.back == data->queue.ringSize) - data->queue.back = 0; - data->queue.size++; - } - else - std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; - } - - iOffset += pMusic->ByteCount; - - // re-align on 32 bits - if ( iOffset % 4 != 0 ) - iOffset += (4 - iOffset % 4); - } - } - else - break; - } - return 0; -} - -MidiInWinKS :: MidiInWinKS( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) -{ - initialize( clientName ); -} - -void MidiInWinKS :: initialize( const std::string& clientName ) -{ - WindowsKsData* data = new WindowsKsData; - apiData_ = (void*)data; - inputData_.apiData = data; - - GUID const aguidEnumCats[] = - { - { STATIC_KSCATEGORY_AUDIO }, { STATIC_KSCATEGORY_CAPTURE } - }; - data->m_pCaptureEnum.reset(new CKsEnumFilters ); - data->m_pCaptureEnum->EnumFilters(aguidEnumCats, 2); -} - -MidiInWinKS :: ~MidiInWinKS() -{ - WindowsKsData* data = static_cast(apiData_); - try { - if ( data->m_pPin ) - closePort(); - } - catch(...) { - } - - delete data; -} - -void MidiInWinKS :: openPort( unsigned int portNumber, const std::string portName ) -{ - WindowsKsData* data = static_cast(apiData_); - - if ( portNumber < 0 || portNumber >= data->m_pCaptureEnum->m_Filters.size() ) { - std::stringstream ost; - ost << "MidiInWinKS::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - CKsMidiCapFilter* pFilter = data->m_pCaptureEnum->m_Filters[portNumber]; - data->m_pPin = pFilter->CreateCapturePin(); - - if ( data->m_pPin == NULL ) { - std::stringstream ost; - ost << "MidiInWinKS::openPort: KS error opening port (could not create pin)"; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - data->m_pPin->SetState(KSSTATE_RUN); - - DWORD threadId; - data->m_hInputThread = ::CreateThread(NULL, 0, &midiKsInputThread, &inputData_, 0, &threadId); - if ( data->m_hInputThread == NULL ) { - std::stringstream ost; - ost << "MidiInWinKS::initialize: Could not create input thread : Windows error " << GetLastError() << std::endl;; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - connected_ = true; -} - -void MidiInWinKS :: openVirtualPort( const std::string portName ) -{ - // This function cannot be implemented for the Windows KS MIDI API. - errorString_ = "MidiInWinKS::openVirtualPort: cannot be implemented in Windows KS MIDI API!"; - RtMidi::error( RtError::WARNING, errorString_ ); -} - -unsigned int MidiInWinKS :: getPortCount() -{ - WindowsKsData* data = static_cast(apiData_); - return (unsigned int)data->m_pCaptureEnum->m_Filters.size(); -} - -std::string MidiInWinKS :: getPortName(unsigned int portNumber) -{ - WindowsKsData* data = static_cast(apiData_); - - if(portNumber < 0 || portNumber >= data->m_pCaptureEnum->m_Filters.size()) { - std::stringstream ost; - ost << "MidiInWinKS::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - CKsMidiCapFilter* pFilter = data->m_pCaptureEnum->m_Filters[portNumber]; - return pFilter->GetFriendlyName(); -} - -void MidiInWinKS :: closePort() -{ - WindowsKsData* data = static_cast(apiData_); - connected_ = false; - - if(data->m_hInputThread) { - ::SignalObjectAndWait(data->m_hExitEvent, data->m_hInputThread, INFINITE, FALSE); - ::CloseHandle(data->m_hInputThread); - } - - if(data->m_pPin) { - data->m_pPin->SetState(KSSTATE_PAUSE); - data->m_pPin->SetState(KSSTATE_STOP); - data->m_pPin->ClosePin(); - data->m_pPin = NULL; - } -} - -// *********************************************************************// -// API: WINDOWS Kernel Streaming -// Class Definitions: MidiOutWinKS -// *********************************************************************// - -MidiOutWinKS :: MidiOutWinKS( const std::string clientName ) : MidiOutApi() -{ - initialize( clientName ); -} - -void MidiOutWinKS :: initialize( const std::string& clientName ) -{ - WindowsKsData* data = new WindowsKsData; - - data->m_pPin = NULL; - data->m_pRenderEnum.reset(new CKsEnumFilters ); - GUID const aguidEnumCats[] = - { - { STATIC_KSCATEGORY_AUDIO }, { STATIC_KSCATEGORY_RENDER } - }; - data->m_pRenderEnum->EnumFilters(aguidEnumCats, 2); - - apiData_ = (void*)data; -} - -MidiOutWinKS :: ~MidiOutWinKS() -{ - // Close a connection if it exists. - closePort(); - - // Cleanup. - WindowsKsData* data = static_cast(apiData_); - delete data; -} - -void MidiOutWinKS :: openPort( unsigned int portNumber, const std::string portName ) -{ - WindowsKsData* data = static_cast(apiData_); - - if(portNumber < 0 || portNumber >= data->m_pRenderEnum->m_Filters.size()) { - std::stringstream ost; - ost << "MidiOutWinKS::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - CKsMidiRenFilter* pFilter = data->m_pRenderEnum->m_Filters[portNumber]; - data->m_pPin = pFilter->CreateRenderPin(); - - if(data->m_pPin == NULL) { - std::stringstream ost; - ost << "MidiOutWinKS::openPort: KS error opening port (could not create pin)"; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - data->m_pPin->SetState(KSSTATE_RUN); - connected_ = true; -} - -void MidiOutWinKS :: openVirtualPort( const std::string portName ) -{ - // This function cannot be implemented for the Windows KS MIDI API. - errorString_ = "MidiOutWinKS::openVirtualPort: cannot be implemented in Windows KS MIDI API!"; - RtMidi::error( RtError::WARNING, errorString_ ); -} - -unsigned int MidiOutWinKS :: getPortCount() -{ - WindowsKsData* data = static_cast(apiData_); - - return (unsigned int)data->m_pRenderEnum->m_Filters.size(); -} - -std::string MidiOutWinKS :: getPortName( unsigned int portNumber ) -{ - WindowsKsData* data = static_cast(apiData_); - - if ( portNumber < 0 || portNumber >= data->m_pRenderEnum->m_Filters.size() ) { - std::stringstream ost; - ost << "MidiOutWinKS::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - CKsMidiRenFilter* pFilter = data->m_pRenderEnum->m_Filters[portNumber]; - return pFilter->GetFriendlyName(); -} - -void MidiOutWinKS :: closePort() -{ - WindowsKsData* data = static_cast(apiData_); - connected_ = false; - - if ( data->m_pPin ) { - data->m_pPin->SetState(KSSTATE_PAUSE); - data->m_pPin->SetState(KSSTATE_STOP); - data->m_pPin->ClosePin(); - data->m_pPin = NULL; - } -} - -void MidiOutWinKS :: sendMessage(std::vector* pMessage) -{ - std::vector const& msg = *pMessage; - WindowsKsData* data = static_cast(apiData_); - size_t iNumMidiBytes = msg.size(); - size_t pos = 0; - - // write header - KSMUSICFORMAT* pKsMusicFormat = reinterpret_cast(&data->m_Buffer[pos]); - pKsMusicFormat->TimeDeltaMs = 0; - pKsMusicFormat->ByteCount = iNumMidiBytes; - pos += sizeof(KSMUSICFORMAT); - - // write MIDI bytes - if ( pos + iNumMidiBytes > data->m_Buffer.size() ) { - std::stringstream ost; - ost << "KsMidiInput::Write: MIDI buffer too small. Required " << pos + iNumMidiBytes << " bytes, only has " << data->m_Buffer.size(); - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - if ( data->m_pPin == NULL ) { - std::stringstream ost; - ost << "MidiOutWinKS::sendMessage: port is not open"; - errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); - } - - memcpy(&data->m_Buffer[pos], &msg[0], iNumMidiBytes); - pos += iNumMidiBytes; - - KSSTREAM_HEADER packet; - memset(&packet, 0, sizeof packet); - packet.Size = sizeof(packet); - packet.PresentationTime.Time = 0; - packet.PresentationTime.Numerator = 1; - packet.PresentationTime.Denominator = 1; - packet.Data = const_cast(&data->m_Buffer[0]); - packet.DataUsed = ((pos+3)/4)*4; - packet.FrameExtent = data->m_Buffer.size(); - - data->m_pPin->WriteData(&packet, NULL); -} - -#endif // __WINDOWS_KS__ //*********************************************************************// // API: UNIX JACK @@ -3404,12 +2455,12 @@ struct JackMidiData { // Class Definitions: MidiInJack //*********************************************************************// -int jackProcessIn( jack_nframes_t nframes, void *arg ) +static int jackProcessIn( jack_nframes_t nframes, void *arg ) { JackMidiData *jData = (JackMidiData *) arg; MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; jack_midi_event_t event; - jack_time_t long long time; + jack_time_t time; // Is port created? if ( jData->port == NULL ) return 0; @@ -3417,13 +2468,13 @@ int jackProcessIn( jack_nframes_t nframes, void *arg ) // We have midi events in buffer int evCount = jack_midi_get_event_count( buff ); - if ( evCount > 0 ) { + for (int j = 0; j < evCount; j++) { MidiInApi::MidiMessage message; message.bytes.clear(); - jack_midi_event_get( &event, buff, 0 ); + jack_midi_event_get( &event, buff, j ); - for (unsigned int i = 0; i < event.size; i++ ) + for ( unsigned int i = 0; i < event.size; i++ ) message.bytes.push_back( event.buffer[i] ); // Compute the delta time. @@ -3467,15 +2518,26 @@ void MidiInJack :: initialize( const std::string& clientName ) JackMidiData *data = new JackMidiData; apiData_ = (void *) data; - // Initialize JACK client - if (( data->client = jack_client_open( clientName.c_str(), JackNullOption, NULL )) == 0) { - errorString_ = "MidiInJack::initialize: JACK server not running?"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); - return; - } - data->rtMidiIn = &inputData_; data->port = NULL; + data->client = NULL; + this->clientName = clientName; + + connect(); +} + +void MidiInJack :: connect() +{ + JackMidiData *data = static_cast (apiData_); + if ( data->client ) + return; + + // Initialize JACK client + if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { + errorString_ = "MidiInJack::initialize: JACK server not running?"; + error( RtMidiError::WARNING, errorString_ ); + return; + } jack_set_process_callback( data->client, jackProcessIn, data ); jack_activate( data->client ); @@ -3486,21 +2548,26 @@ MidiInJack :: ~MidiInJack() JackMidiData *data = static_cast (apiData_); closePort(); - jack_client_close( data->client ); + if ( data->client ) + jack_client_close( data->client ); + delete data; } void MidiInJack :: openPort( unsigned int portNumber, const std::string portName ) { JackMidiData *data = static_cast (apiData_); + connect(); + // Creating new port if ( data->port == NULL) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); if ( data->port == NULL) { - errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + errorString_ = "MidiInJack::openPort: JACK error creating port"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Connecting to the output @@ -3512,13 +2579,14 @@ void MidiInJack :: openVirtualPort( const std::string portName ) { JackMidiData *data = static_cast (apiData_); + connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); } } @@ -3526,6 +2594,9 @@ unsigned int MidiInJack :: getPortCount() { int count = 0; JackMidiData *data = static_cast (apiData_); + connect(); + if ( !data->client ) + return 0; // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); @@ -3542,9 +2613,10 @@ unsigned int MidiInJack :: getPortCount() std::string MidiInJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); - std::ostringstream ost; std::string retStr(""); + connect(); + // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); @@ -3552,19 +2624,19 @@ std::string MidiInJack :: getPortName( unsigned int portNumber ) // Check port validity if ( ports == NULL ) { errorString_ = "MidiInJack::getPortName: no ports available!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return retStr; } if ( ports[portNumber] == NULL ) { + std::ostringstream ost; ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } else retStr.assign( ports[portNumber] ); free( ports ); - return retStr; } @@ -3583,7 +2655,7 @@ void MidiInJack :: closePort() //*********************************************************************// // Jack process callback -int jackProcessOut( jack_nframes_t nframes, void *arg ) +static int jackProcessOut( jack_nframes_t nframes, void *arg ) { JackMidiData *data = (JackMidiData *) arg; jack_midi_data_t *midiData; @@ -3613,13 +2685,25 @@ MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi() void MidiOutJack :: initialize( const std::string& clientName ) { JackMidiData *data = new JackMidiData; + apiData_ = (void *) data; data->port = NULL; + data->client = NULL; + this->clientName = clientName; + + connect(); +} + +void MidiOutJack :: connect() +{ + JackMidiData *data = static_cast (apiData_); + if ( data->client ) + return; // Initialize JACK client - if (( data->client = jack_client_open( clientName.c_str(), JackNullOption, NULL )) == 0) { + if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { errorString_ = "MidiOutJack::initialize: JACK server not running?"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return; } @@ -3627,8 +2711,6 @@ void MidiOutJack :: initialize( const std::string& clientName ) data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); jack_activate( data->client ); - - apiData_ = (void *) data; } MidiOutJack :: ~MidiOutJack() @@ -3636,10 +2718,12 @@ MidiOutJack :: ~MidiOutJack() JackMidiData *data = static_cast (apiData_); closePort(); - // Cleanup - jack_client_close( data->client ); - jack_ringbuffer_free( data->buffSize ); - jack_ringbuffer_free( data->buffMessage ); + if ( data->client ) { + // Cleanup + jack_client_close( data->client ); + jack_ringbuffer_free( data->buffSize ); + jack_ringbuffer_free( data->buffMessage ); + } delete data; } @@ -3648,14 +2732,17 @@ void MidiOutJack :: openPort( unsigned int portNumber, const std::string portNam { JackMidiData *data = static_cast (apiData_); + connect(); + // Creating new port if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { - errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + errorString_ = "MidiOutJack::openPort: JACK error creating port"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; } // Connecting to the output @@ -3667,13 +2754,14 @@ void MidiOutJack :: openVirtualPort( const std::string portName ) { JackMidiData *data = static_cast (apiData_); + connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; - RtMidi::error( RtError::DRIVER_ERROR, errorString_ ); + error( RtMidiError::DRIVER_ERROR, errorString_ ); } } @@ -3681,6 +2769,9 @@ unsigned int MidiOutJack :: getPortCount() { int count = 0; JackMidiData *data = static_cast (apiData_); + connect(); + if ( !data->client ) + return 0; // List of available ports const char **ports = jack_get_ports( data->client, NULL, @@ -3698,9 +2789,10 @@ unsigned int MidiOutJack :: getPortCount() std::string MidiOutJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); - std::ostringstream ost; std::string retStr(""); + connect(); + // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); @@ -3708,19 +2800,19 @@ std::string MidiOutJack :: getPortName( unsigned int portNumber ) // Check port validity if ( ports == NULL) { errorString_ = "MidiOutJack::getPortName: no ports available!"; - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); return retStr; } if ( ports[portNumber] == NULL) { + std::ostringstream ost; ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); - RtMidi::error( RtError::WARNING, errorString_ ); + error( RtMidiError::WARNING, errorString_ ); } else retStr.assign( ports[portNumber] ); free( ports ); - return retStr; } diff --git a/src/RtWvIn.cpp b/src/RtWvIn.cpp index 1be01bb..4ed00cb 100644 --- a/src/RtWvIn.cpp +++ b/src/RtWvIn.cpp @@ -86,7 +86,7 @@ RtWvIn :: RtWvIn( unsigned int nChannels, StkFloat sampleRate, int device, int b try { adc_.openStream( NULL, ¶meters, format, (unsigned int)Stk::sampleRate(), &size, &read, (void *)this ); } - catch ( RtError &error ) { + catch ( RtAudioError &error ) { handleError( error.what(), StkError::AUDIO_SYSTEM ); } diff --git a/src/RtWvOut.cpp b/src/RtWvOut.cpp index 52bd80d..fa780b1 100644 --- a/src/RtWvOut.cpp +++ b/src/RtWvOut.cpp @@ -107,7 +107,7 @@ RtWvOut :: RtWvOut( unsigned int nChannels, StkFloat sampleRate, int device, int try { dac_.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &size, &write, (void *)this ); } - catch ( RtError &error ) { + catch ( RtAudioError &error ) { handleError( error.what(), StkError::AUDIO_SYSTEM ); }