diff --git a/include/RtAudio.h b/include/RtAudio.h index 1aa42e0..e767ddb 100644 --- a/include/RtAudio.h +++ b/include/RtAudio.h @@ -11,7 +11,7 @@ RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes - Copyright (c) 2001-2021 Gary P. Scavone + Copyright (c) 2001-2023 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -46,7 +46,24 @@ #ifndef __RTAUDIO_H #define __RTAUDIO_H -#define RTAUDIO_VERSION "5.2.0" +#define RTAUDIO_VERSION_MAJOR 6 +#define RTAUDIO_VERSION_MINOR 0 +#define RTAUDIO_VERSION_PATCH 1 +#define RTAUDIO_VERSION_BETA 0 + +#define RTAUDIO_TOSTRING2(n) #n +#define RTAUDIO_TOSTRING(n) RTAUDIO_TOSTRING2(n) + +#if RTAUDIO_VERSION_BETA > 0 + #define RTAUDIO_VERSION RTAUDIO_TOSTRING(RTAUDIO_VERSION_MAJOR) \ + "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_MINOR) \ + "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_PATCH) \ + "beta" RTAUDIO_TOSTRING(RTAUDIO_VERSION_BETA) +#else + #define RTAUDIO_VERSION RTAUDIO_TOSTRING(RTAUDIO_VERSION_MAJOR) \ + "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_MINOR) \ + "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_PATCH) +#endif #if defined _WIN32 || defined __CYGWIN__ #if defined(RTAUDIO_EXPORT) @@ -64,8 +81,8 @@ #include #include -#include #include +#include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. @@ -75,6 +92,8 @@ 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 there are no range checks for floating-point values that + extend beyond plus/minus 1.0. - \e RTAUDIO_SINT8: 8-bit signed integer. - \e RTAUDIO_SINT16: 16-bit signed integer. @@ -206,52 +225,19 @@ 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 RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error -{ - 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 occurred during memory allocation. */ - INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ - INVALID_USE, /*!< The function was called incorrectly. */ - DRIVER_ERROR, /*!< A system driver error occurred. */ - SYSTEM_ERROR, /*!< A system error occurred. */ - THREAD_ERROR /*!< A thread error occurred. */ - }; - - //! The constructor. - RtAudioError( const std::string& message, - Type type = RtAudioError::UNSPECIFIED ) - : std::runtime_error(message), type_(type) {} - - //! Prints thrown error message to stderr. - virtual void printMessage( void ) const - { std::cerr << '\n' << what() << "\n\n"; } - - //! Returns the thrown error message type. - virtual const Type& getType(void) const { return type_; } - - //! Returns the thrown error message string. - virtual const std::string getMessage(void) const - { return std::string(what()); } - - protected: - Type type_; +enum RtAudioErrorType { + RTAUDIO_NO_ERROR = 0, /*!< No error. */ + RTAUDIO_WARNING, /*!< A non-critical error. */ + RTAUDIO_UNKNOWN_ERROR, /*!< An unspecified error type. */ + RTAUDIO_NO_DEVICES_FOUND, /*!< No devices found on system. */ + RTAUDIO_INVALID_DEVICE, /*!< An invalid device ID was specified. */ + RTAUDIO_DEVICE_DISCONNECT, /*!< A device in use was disconnected. */ + RTAUDIO_MEMORY_ERROR, /*!< An error occurred during memory allocation. */ + RTAUDIO_INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + RTAUDIO_INVALID_USE, /*!< The function was called incorrectly. */ + RTAUDIO_DRIVER_ERROR, /*!< A system driver error occurred. */ + RTAUDIO_SYSTEM_ERROR, /*!< A system error occurred. */ + RTAUDIO_THREAD_ERROR /*!< A thread error occurred. */ }; //! RtAudio error callback function prototype. @@ -259,7 +245,9 @@ class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error \param type Type of error. \param errorText Error description. */ -typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); +typedef std::function + RtAudioErrorCallback; // **************************************************************** // // @@ -283,13 +271,13 @@ class RTAUDIO_DLL_PUBLIC RtAudio //! Audio API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ + MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ LINUX_PULSE, /*!< The Linux PulseAudio API. */ 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_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_DS, /*!< The Microsoft DirectSound API. */ RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ NUM_APIS /*!< Number of values in this enum. */ @@ -297,21 +285,23 @@ class RTAUDIO_DLL_PUBLIC RtAudio //! The public device information structure for returning queried values. struct DeviceInfo { - bool probed; /*!< true if the device capabilities were successfully probed. */ - std::string name; /*!< Character string device identifier. */ + unsigned int ID{}; /*!< Device ID used to specify a device to RtAudio. */ + std::string name; /*!< Character string device name. */ unsigned int outputChannels{}; /*!< Maximum output channels supported by device. */ unsigned int inputChannels{}; /*!< Maximum input channels supported by device. */ unsigned int duplexChannels{}; /*!< Maximum simultaneous input/output channels supported by device. */ bool isDefaultOutput{false}; /*!< true if this is the default output device. */ bool isDefaultInput{false}; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ + unsigned int currentSampleRate{}; /*!< Current sample rate, system sample rate as currently configured. */ unsigned int preferredSampleRate{}; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ RtAudioFormat nativeFormats{}; /*!< Bit mask of supported data formats. */ }; //! The structure for specifying input or output stream parameters. struct StreamParameters { - unsigned int deviceId{}; /*!< Device index (0 to getDeviceCount() - 1). */ + //std::string deviceName{}; /*!< Device name from device list. */ + unsigned int deviceId{}; /*!< Device id as provided by getDeviceIds(). */ unsigned int nChannels{}; /*!< Number of channels. */ unsigned int firstChannel{}; /*!< First channel index on device (default = 0). */ }; @@ -369,9 +359,11 @@ class RTAUDIO_DLL_PUBLIC RtAudio function by the value actually used by the system. The \c streamName parameter can be used to set the client name - when using the Jack API. By default, the client name is set to - RtApiJack. However, if you wish to create multiple instances of - RtAudio with Jack, each instance must have a unique client name. + when using the Jack API or the application name when using the + Pulse API. By default, the Jack client name is set to RtApiJack. + However, if you wish to create multiple instances of RtAudio with + Jack, each instance must have a unique client name. The default + Pulse application name is set to "RtAudio." */ struct StreamOptions { RtAudioStreamFlags flags{}; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ @@ -409,21 +401,36 @@ class RTAUDIO_DLL_PUBLIC RtAudio //! Return the compiled audio API having the given name. /*! A case insensitive comparison will check the specified name - against the list of compiled APIs, and return the one which + against the list of compiled APIs, and return the one that matches. On failure, the function returns UNSPECIFIED. */ static RtAudio::Api getCompiledApiByName( const std::string &name ); + //! Return the compiled audio API having the given display name. + /*! + A case sensitive comparison will check the specified display name + against the list of compiled APIs, and return the one that + matches. On failure, the function returns UNSPECIFIED. + */ + static RtAudio::Api getCompiledApiByDisplayName( const std::string &name ); + //! The class constructor. /*! - The constructor performs minor initialization tasks. An exception - can be thrown if no API support is compiled. + The constructor attempts to create an RtApi instance. - 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). + If an API argument is specified but that API has not been + compiled, a warning is issued and an instance of an available API + is created. If no compiled API is found, the routine will abort + (though this should be impossible because RtDummy is the default + if no API-specific preprocessor definition is provided to the + compiler). 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). + + An optional errorCallback function can be specified to + subsequently receive warning and error messages. */ - RtAudio( RtAudio::Api api=UNSPECIFIED ); + RtAudio( RtAudio::Api api=UNSPECIFIED, RtAudioErrorCallback&& errorCallback=0 ); //! The destructor. /*! @@ -437,65 +444,89 @@ class RTAUDIO_DLL_PUBLIC RtAudio //! A public function that queries for the number of audio devices available. /*! - This function performs a system query of available devices each time it - is called, thus supporting devices connected \e after instantiation. If - a system error occurs during processing, a warning will be issued. + This function performs a system query of available devices each + time it is called, thus supporting devices (dis)connected \e after + instantiation. If a system error occurs during processing, a + warning will be issued. */ unsigned int getDeviceCount( void ); - //! Return an RtAudio::DeviceInfo structure for a specified device number. + //! A public function that returns a vector of audio device IDs. /*! - - Any device integer between 0 and getDeviceCount() - 1 is valid. - 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 - current default input or output device, the corresponding - "isDefault" member will have a value of "true". + The ID values returned by this function are used internally by + RtAudio to identify a given device. The values themselves are + arbitrary and do not correspond to device IDs used by the + underlying API (nor are they index values). This function performs + a system query of available devices each time it is called, thus + supporting devices (dis)connected \e after instantiation. If no + devices are available, the vector size will be zero. If a system + error occurs during processing, a warning will be issued. */ - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + std::vector getDeviceIds( void ); - //! A function that returns the index of the default output device. + //! A public function that returns a vector of audio device names. /*! - If the underlying audio API does not provide a "default - device", or if no devices are available, the return value will be - 0. Note that this is a valid device identifier and it is the - client's responsibility to verify that a device is available - before attempting to open a stream. + This function performs a system query of available devices each + time it is called, thus supporting devices (dis)connected \e after + instantiation. If no devices are available, the vector size will + be zero. If a system error occurs during processing, a warning + will be issued. + */ + std::vector getDeviceNames( void ); + + //! Return an RtAudio::DeviceInfo structure for a specified device ID. + /*! + Any device ID returned by getDeviceIds() is valid, unless it has + been removed between the call to getDevceIds() and this + function. If an invalid argument is provided, an + RTAUDIO_INVALID_USE will be passed to the user-provided + errorCallback function (or otherwise printed to stderr) and all + members of the returned RtAudio::DeviceInfo structure will be + initialized to default, invalid values (ID = 0, empty name, ...). + If the specified device is the current default input or output + device, the corresponding "isDefault" member will have a value of + "true". + */ + RtAudio::DeviceInfo getDeviceInfo( unsigned int deviceId ); + + //! A function that returns the ID of the default output device. + /*! + If the underlying audio API does not provide a "default device", + the first probed output device ID will be returned. If no devices + are available, the return value will be 0 (which is an invalid + device identifier). */ unsigned int getDefaultOutputDevice( void ); - //! A function that returns the index of the default input device. + //! A function that returns the ID of the default input device. /*! - If the underlying audio API does not provide a "default - device", or if no devices are available, the return value will be - 0. Note that this is a valid device identifier and it is the - client's responsibility to verify that a device is available - before attempting to open a stream. + If the underlying audio API does not provide a "default device", + the first probed input device ID will be returned. If no devices + are available, the return value will be 0 (which is an invalid + device identifier). */ unsigned int getDefaultInputDevice( void ); //! A public function for opening a stream with the specified parameters. /*! - An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be + An RTAUDIO_SYSTEM_ERROR is returned if a stream cannot be opened with the specified parameters or an error occurs during - processing. An RtAudioError (type = INVALID_USE) is thrown if any - invalid device ID or channel number parameters are specified. + processing. An RTAUDIO_INVALID_USE is returned if a stream + is already open or any invalid stream parameters are specified. \param outputParameters Specifies output stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For input-only streams, this - argument should be NULL. The device ID is an index value between - 0 and getDeviceCount() - 1. + argument should be NULL. The device ID is a value returned by + getDeviceIds(). \param inputParameters Specifies input stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For output-only streams, this - argument should be NULL. The device ID is an index value between - 0 and getDeviceCount() - 1. + argument should be NULL. The device ID is a value returned by + getDeviceIds(). \param format An RtAudioFormat specifying the desired sample data format. \param sampleRate The desired sample rate (sample frames per second). - \param *bufferFrames A pointer to a value indicating the desired + \param bufferFrames A pointer to a value indicating the desired internal buffer size in sample frames. The actual value used by the device is returned via the same pointer. A value of zero can be specified, in which case the lowest @@ -513,48 +544,52 @@ class RTAUDIO_DLL_PUBLIC 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 occurred. */ - 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, RtAudioErrorCallback errorCallback = NULL ); + RtAudioErrorType openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData = NULL, RtAudio::StreamOptions *options = NULL ); //! A function that closes a stream and frees any associated stream memory. /*! - If a stream is not open, this function issues a warning and - returns (no exception is thrown). + If a stream is not open, an RTAUDIO_WARNING will be passed to the + user-provided errorCallback function (or otherwise printed to + stderr). */ void closeStream( void ); //! A function that starts a stream. /*! - 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. + An RTAUDIO_SYSTEM_ERROR is returned if an error occurs during + processing. An RTAUDIO_WARNING is returned if a stream is not open + or is already running. */ - void startStream( void ); + RtAudioErrorType startStream( void ); //! Stop a stream, allowing any samples remaining in the output queue to be played. /*! - 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. + An RTAUDIO_SYSTEM_ERROR is returned if an error occurs during + processing. An RTAUDIO_WARNING is returned if a stream is not + open or is already stopped. */ - void stopStream( void ); + RtAudioErrorType stopStream( void ); //! Stop a stream, discarding any samples remaining in the input/output queue. /*! - 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. + An RTAUDIO_SYSTEM_ERROR is returned if an error occurs during + processing. An RTAUDIO_WARNING is returned if a stream is not + open or is already stopped. */ - void abortStream( void ); + RtAudioErrorType abortStream( void ); + + //! Retrieve the error message corresponding to the last error or warning condition. + /*! + This function can be used to get a detailed error message when a + non-zero RtAudioErrorType is returned by a function. This is the + same message sent to the user-provided errorCallback function. + */ + const std::string getErrorText( void ); //! Returns true if a stream is open and false if not. bool isStreamOpen( void ) const; @@ -562,16 +597,17 @@ class RTAUDIO_DLL_PUBLIC RtAudio //! Returns true if the stream is running and false if it is stopped or not open. bool isStreamRunning( void ) const; - //! Returns the number of elapsed seconds since the stream was started. + //! Returns the number of seconds of processed data since the stream was started. /*! - If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + The stream time is calculated from the number of sample frames + processed by the underlying audio system, which will increment by + units of the audio buffer size. It is not an absolute running + time. If a stream is not open, the returned value may not be + valid. */ double getStreamTime( void ); //! Set the stream time to a time in seconds greater than or equal to 0.0. - /*! - If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. - */ void setStreamTime( double time ); //! Returns the internal stream latency in sample frames. @@ -579,21 +615,29 @@ class RTAUDIO_DLL_PUBLIC RtAudio The stream latency refers to delay in audio input and/or output 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 - RtAudioError (type = INVALID_USE) will be thrown. If the API does not - report latency, the return value will be zero. + the input and output latencies. If a stream is not open, the + returned value will be invalid. If the API does not report + latency, the return value will be zero. */ long getStreamLatency( void ); - //! Returns actual sample rate in use by the stream. - /*! - 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 RtAudioError (type = INVALID_USE) will be thrown. - */ + //! Returns actual sample rate in use by the (open) stream. + /*! + On some systems, the sample rate used may be slightly different + than that specified in the stream parameters. If a stream is not + open, a value of zero is returned. + */ unsigned int getStreamSampleRate( void ); - //! Specify whether warning messages should be printed to stderr. + //! Set a client-defined function that will be invoked when an error or warning occurs. + void setErrorCallback( RtAudioErrorCallback errorCallback ); + + //! Specify whether warning messages should be output or not. + /*! + The default behaviour is for warning messages to be output, + either to a client-defined error callback function (if specified) + or to stderr. + */ void showWarnings( bool value = true ); protected: @@ -603,7 +647,7 @@ class RTAUDIO_DLL_PUBLIC RtAudio }; // Operating system dependent thread functionality. -#if defined(_WIN32) || defined(__CYGWIN__) +#if defined(_MSC_VER) #ifndef NOMINMAX #define NOMINMAX @@ -642,11 +686,11 @@ struct CallbackInfo { ThreadHandle thread{}; void *callback{}; void *userData{}; - void *errorCallback{}; void *apiInfo{}; // void pointer for API specific callback information bool isRunning{false}; bool doRealtime{false}; int priority{}; + bool deviceDisconnected{false}; }; // **************************************************************** // @@ -705,26 +749,29 @@ public: RtApi(); virtual ~RtApi(); virtual RtAudio::Api getCurrentApi( void ) = 0; - virtual unsigned int getDeviceCount( void ) = 0; - virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; + unsigned int getDeviceCount( void ); + std::vector getDeviceIds( void ); + std::vector getDeviceNames( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int deviceId ); virtual unsigned int getDefaultInputDevice( void ); virtual unsigned int getDefaultOutputDevice( void ); - void openStream( RtAudio::StreamParameters *outputParameters, - RtAudio::StreamParameters *inputParameters, - RtAudioFormat format, unsigned int sampleRate, - unsigned int *bufferFrames, RtAudioCallback callback, - void *userData, RtAudio::StreamOptions *options, - RtAudioErrorCallback errorCallback ); + RtAudioErrorType openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData, RtAudio::StreamOptions *options ); virtual void closeStream( void ); - virtual void startStream( void ) = 0; - virtual void stopStream( void ) = 0; - virtual void abortStream( void ) = 0; + virtual RtAudioErrorType startStream( void ) = 0; + virtual RtAudioErrorType stopStream( void ) = 0; + virtual RtAudioErrorType abortStream( void ) = 0; + const std::string getErrorText( void ) const { return errorText_; } long getStreamLatency( void ); unsigned int getStreamSampleRate( void ); - virtual double getStreamTime( void ); + virtual double getStreamTime( void ) const { return stream_.streamTime; } virtual void setStreamTime( double time ); bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } + void setErrorCallback( RtAudioErrorCallback errorCallback ) { errorCallback_ = errorCallback; } void showWarnings( bool value ) { showWarnings_ = value; } @@ -760,7 +807,7 @@ protected: // A protected structure for audio streams. struct RtApiStream { - unsigned int device[2]; // Playback and record, respectively. + unsigned int deviceId[2]; // Playback and record, respectively. void *apiHandle; // void pointer for API specific stream handle information StreamMode mode; // OUTPUT, INPUT, or DUPLEX. StreamState state; // STOPPED, RUNNING, or CLOSED @@ -789,7 +836,7 @@ protected: #endif RtApiStream() - :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } + :apiHandle(0), deviceBuffer(0) {} // { device[0] = std::string(); device[1] = std::string(); } }; typedef S24 Int24; @@ -800,10 +847,22 @@ protected: std::ostringstream errorStream_; std::string errorText_; + RtAudioErrorCallback errorCallback_; bool showWarnings_; + std::vector deviceList_; + unsigned int currentDeviceId_; RtApiStream stream_; - bool firstErrorOccurred_; + /*! + Protected, api-specific method that attempts to probe all device + capabilities in a system. The function will not re-probe devices + that were previously found and probed. This function MUST be + implemented by all subclasses. If an error is encountered during + the probe, a "warning" message may be reported and the internal + list of devices may be incomplete. + */ + virtual void probeDevices( void ); + /*! Protected, api-specific method that attempts to open a device with the given parameters. This function MUST be implemented by @@ -811,7 +870,7 @@ protected: "warning" message is reported and FAILURE is returned. A successful probe is indicated by a return value of SUCCESS. */ - virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + virtual bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -822,14 +881,8 @@ protected: //! Protected common method to clear an RtApiStream structure. void clearStreamInfo(); - /*! - 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( RtAudioError::Type type ); + RtAudioErrorType error( RtAudioErrorType type ); /*! Protected method used to perform format, channel number, and/or interleaving @@ -855,328 +908,25 @@ protected: inline RtAudio::Api RtAudio :: getCurrentApi( void ) { return rtapi_->getCurrentApi(); } inline unsigned int RtAudio :: getDeviceCount( void ) { return rtapi_->getDeviceCount(); } -inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } +inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int deviceId ) { return rtapi_->getDeviceInfo( deviceId ); } +inline std::vector RtAudio :: getDeviceIds( void ) { return rtapi_->getDeviceIds(); } +inline std::vector RtAudio :: getDeviceNames( void ) { return rtapi_->getDeviceNames(); } inline unsigned int RtAudio :: getDefaultInputDevice( void ) { return rtapi_->getDefaultInputDevice(); } inline unsigned int RtAudio :: getDefaultOutputDevice( void ) { return rtapi_->getDefaultOutputDevice(); } inline void RtAudio :: closeStream( void ) { return rtapi_->closeStream(); } -inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } -inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } -inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } +inline RtAudioErrorType RtAudio :: startStream( void ) { return rtapi_->startStream(); } +inline RtAudioErrorType RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } +inline RtAudioErrorType RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } +inline const std::string RtAudio :: getErrorText( void ) { return rtapi_->getErrorText(); } inline bool RtAudio :: isStreamOpen( void ) const { return rtapi_->isStreamOpen(); } inline bool RtAudio :: isStreamRunning( void ) const { return rtapi_->isStreamRunning(); } inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } +inline void RtAudio :: setErrorCallback( RtAudioErrorCallback errorCallback ) { rtapi_->setErrorCallback( errorCallback ); } inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } -// RtApi Subclass prototypes. - -#if defined(__MACOSX_CORE__) - -#include - -class RtApiCore: public RtApi -{ -public: - - RtApiCore(); - ~RtApiCore(); - RtAudio::Api getCurrentApi( void ) override { return RtAudio::MACOSX_CORE; } - unsigned int getDeviceCount( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - unsigned int getDefaultOutputDevice( void ) override; - unsigned int getDefaultInputDevice( void ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - bool callbackEvent( AudioDeviceID deviceId, - const AudioBufferList *inBufferList, - const AudioBufferList *outBufferList ); - - 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 ) override; - static const char* getErrorCode( OSStatus code ); -}; - -#endif - -#if defined(__UNIX_JACK__) - -class RtApiJack: public RtApi -{ -public: - - RtApiJack(); - ~RtApiJack(); - RtAudio::Api getCurrentApi( void ) override { return RtAudio::UNIX_JACK; } - unsigned int getDeviceCount( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - bool callbackEvent( unsigned long nframes ); - - 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 ) override; - - bool shouldAutoconnect_; -}; - -#endif - -#if defined(__WINDOWS_ASIO__) - -class RtApiAsio: public RtApi -{ -public: - - RtApiAsio(); - ~RtApiAsio(); - RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_ASIO; } - unsigned int getDeviceCount( void ) override; - unsigned int getDefaultOutputDevice( void ) override; - unsigned int getDefaultInputDevice( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - bool callbackEvent( long bufferIndex ); - - private: - - std::vector devices_; - void saveDeviceInfo( void ); - bool coInitialized_; - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, - unsigned int firstChannel, unsigned int sampleRate, - RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ) override; -}; - -#endif - -#if defined(__WINDOWS_DS__) - -class RtApiDs: public RtApi -{ -public: - - RtApiDs(); - ~RtApiDs(); - RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_DS; } - unsigned int getDeviceCount( void ) override; - unsigned int getDefaultOutputDevice( void ) override; - unsigned int getDefaultInputDevice( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - void callbackEvent( void ); - - private: - - 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, - RtAudio::StreamOptions *options ) override; -}; - -#endif - -#if defined(__WINDOWS_WASAPI__) - -struct IMMDeviceEnumerator; - -class RtApiWasapi : public RtApi -{ -public: - RtApiWasapi(); - virtual ~RtApiWasapi(); - - RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_WASAPI; } - unsigned int getDeviceCount( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - -private: - bool coInitialized_; - 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 ) override; - - 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 -{ -public: - - RtApiAlsa(); - ~RtApiAlsa(); - RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_ALSA; } - unsigned int getDeviceCount( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - void callbackEvent( void ); - - private: - - std::vector devices_; - void saveDeviceInfo( void ); - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, - unsigned int firstChannel, unsigned int sampleRate, - RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ) override; -}; - -#endif - -#if defined(__LINUX_PULSE__) - -class RtApiPulse: public RtApi -{ -public: - ~RtApiPulse(); - RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_PULSE; } - unsigned int getDeviceCount( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - void callbackEvent( void ); - - private: - - void collectDeviceInfo( void ); - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, - unsigned int firstChannel, unsigned int sampleRate, - RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ) override; -}; - -#endif - -#if defined(__LINUX_OSS__) - -class RtApiOss: public RtApi -{ -public: - - RtApiOss(); - ~RtApiOss(); - RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_OSS; } - unsigned int getDeviceCount( void ) override; - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; - void closeStream( void ) override; - void startStream( void ) override; - void stopStream( void ) override; - void abortStream( void ) override; - - // This function is intended for internal use only. It must be - // public because it is called by the internal callback handler, - // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesirable results! - void callbackEvent( 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 ) override; -}; - -#endif - -#if defined(__RTAUDIO_DUMMY__) - -class RtApiDummy: public RtApi -{ -public: - - RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } - RtAudio::Api getCurrentApi( void ) override { return RtAudio::RTAUDIO_DUMMY; } - unsigned int getDeviceCount( void ) override { return 0; } - RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) override { RtAudio::DeviceInfo info; return info; } - void closeStream( void ) override {} - void startStream( void ) override {} - void stopStream( void ) override {} - void abortStream( void ) override {} - - private: - - bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, - unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, - RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, - RtAudio::StreamOptions * /*options*/ ) override { return false; } -}; - -#endif - #endif // Indentation settings for Vim and Emacs diff --git a/include/RtMidi.h b/include/RtMidi.h index a6f5b79..1d64544 100644 --- a/include/RtMidi.h +++ b/include/RtMidi.h @@ -9,7 +9,7 @@ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes - Copyright (c) 2003-2021 Gary P. Scavone + Copyright (c) 2003-2023 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -58,7 +58,24 @@ #endif #endif -#define RTMIDI_VERSION "5.0.0" +#define RTMIDI_VERSION_MAJOR 6 +#define RTMIDI_VERSION_MINOR 0 +#define RTMIDI_VERSION_PATCH 0 +#define RTMIDI_VERSION_BETA 0 + +#define RTMIDI_TOSTRING2(n) #n +#define RTMIDI_TOSTRING(n) RTMIDI_TOSTRING2(n) + +#if RTMIDI_VERSION_BETA > 0 + #define RTMIDI_VERSION RTMIDI_TOSTRING(RTMIDI_VERSION_MAJOR) \ + "." RTMIDI_TOSTRING(RTMIDI_VERSION_MINOR) \ + "." RTMIDI_TOSTRING(RTMIDI_VERSION_PATCH) \ + "beta" RTMIDI_TOSTRING(RTMIDI_VERSION_BETA) +#else + #define RTMIDI_VERSION RTMIDI_TOSTRING(RTMIDI_VERSION_MAJOR) \ + "." RTMIDI_TOSTRING(RTMIDI_VERSION_MINOR) \ + "." RTMIDI_TOSTRING(RTMIDI_VERSION_PATCH) +#endif #include #include @@ -86,12 +103,12 @@ class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception UNSPECIFIED, /*!< The default, unspecified error type. */ NO_DEVICES_FOUND, /*!< No devices found on system. */ INVALID_DEVICE, /*!< An invalid device ID was specified. */ - MEMORY_ERROR, /*!< An error occured during memory allocation. */ + MEMORY_ERROR, /*!< An error occurred during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ - DRIVER_ERROR, /*!< A system driver error occured. */ - SYSTEM_ERROR, /*!< A system error occured. */ - THREAD_ERROR /*!< A thread error occured. */ + DRIVER_ERROR, /*!< A system driver error occurred. */ + SYSTEM_ERROR, /*!< A system error occurred. */ + THREAD_ERROR /*!< A thread error occurred. */ }; //! The constructor. @@ -144,6 +161,8 @@ class RTMIDI_DLL_PUBLIC RtMidi WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ RTMIDI_DUMMY, /*!< A compilable but non-functional API. */ WEB_MIDI_API, /*!< W3C Web MIDI API. */ + WINDOWS_UWP, /*!< The Microsoft Universal Windows Platform MIDI API. */ + ANDROID_AMIDI, /*!< Native Android MIDI API. */ NUM_APIS /*!< Number of values in this enum. */ }; @@ -206,9 +225,9 @@ class RTMIDI_DLL_PUBLIC RtMidi */ virtual bool isPortOpen( void ) const = 0; - //! Set an error callback function to be invoked when an error has occured. + //! Set an error callback function to be invoked when an error has occurred. /*! - The callback function will be called whenever an error has occured. It is best + The callback function will be called whenever an error has occurred. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; @@ -373,9 +392,9 @@ class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi */ double getMessage( std::vector *message ); - //! Set an error callback function to be invoked when an error has occured. + //! Set an error callback function to be invoked when an error has occurred. /*! - The callback function will be called whenever an error has occured. It is best + The callback function will be called whenever an error has occurred. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); @@ -491,9 +510,9 @@ class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi */ void sendMessage( const unsigned char *message, size_t size ); - //! Set an error callback function to be invoked when an error has occured. + //! Set an error callback function to be invoked when an error has occurred. /*! - The callback function will be called whenever an error has occured. It is best + The callback function will be called whenever an error has occurred. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); @@ -559,7 +578,7 @@ class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); void cancelCallback( void ); virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); - double getMessage( std::vector *message ); + virtual double getMessage( std::vector *message ); virtual void setBufferSize( unsigned int size, unsigned int count ); // A MIDI structure used internally by the class to store incoming diff --git a/include/RtWvIn.h b/include/RtWvIn.h index 67062dc..4e6a8a5 100644 --- a/include/RtWvIn.h +++ b/include/RtWvIn.h @@ -33,14 +33,14 @@ class RtWvIn : public WvIn public: //! Default constructor. /*! - The default \e device argument value (zero) will select the + The default \e deviceIndex argument value (zero) will select the default input device on your system. The first device enumerated by the underlying audio API is specified with a value of one. The default buffer size of RT_BUFFER_SIZE is defined in Stk.h. An StkError will be thrown if an error occurs duing instantiation. */ RtWvIn( unsigned int nChannels = 1, StkFloat sampleRate = Stk::sampleRate(), - int device = 0, int bufferFrames = RT_BUFFER_SIZE, int nBuffers = 20 ); + int deviceIndex = 0, int bufferFrames = RT_BUFFER_SIZE, int nBuffers = 20 ); //! Class destructor. ~RtWvIn(); diff --git a/include/RtWvOut.h b/include/RtWvOut.h index f16bc88..47fea83 100644 --- a/include/RtWvOut.h +++ b/include/RtWvOut.h @@ -33,14 +33,14 @@ class RtWvOut : public WvOut //! Default constructor. /*! - The default \e device argument value (zero) will select the + The default \e deviceIndex argument value (zero) will select the default output device on your system. The first device enumerated by the underlying audio API is specified with a value of one. The default buffer size of RT_BUFFER_SIZE is defined in Stk.h. An StkError will be thrown if an error occurs duing instantiation. */ RtWvOut( unsigned int nChannels = 1, StkFloat sampleRate = Stk::sampleRate(), - int device = 0, int bufferFrames = RT_BUFFER_SIZE, int nBuffers = 20 ); + int deviceIndex = 0, int bufferFrames = RT_BUFFER_SIZE, int nBuffers = 20 ); //! Class destructor. ~RtWvOut(); diff --git a/projects/demo/Md2Skini.cpp b/projects/demo/Md2Skini.cpp index dc32e4b..05fe281 100644 --- a/projects/demo/Md2Skini.cpp +++ b/projects/demo/Md2Skini.cpp @@ -40,10 +40,10 @@ void midiCallback( double deltatime, std::vector< unsigned char > *bytes, void * // Parse the MIDI bytes ... only keep MIDI channel messages. if ( bytes->at(0) > 239 ) return; - register long type = bytes->at(0) & 0xF0; - register int channel = bytes->at(0) & 0x0F; - register long databyte1 = bytes->at(1); - register long databyte2 = 0; + long type = bytes->at(0) & 0xF0; + int channel = bytes->at(0) & 0x0F; + long databyte1 = bytes->at(1); + long databyte2 = 0; if ( ( type != 0xC0 ) && ( type != 0xD0 ) ) { if ( bytes->size() < 3 ) return; databyte2 = bytes->at(2); diff --git a/projects/demo/demo.cpp b/projects/demo/demo.cpp index e88a971..e061209 100644 --- a/projects/demo/demo.cpp +++ b/projects/demo/demo.cpp @@ -203,7 +203,8 @@ int main( int argc, char *argv[] ) int i; #if defined(__STK_REALTIME__) - RtAudio dac; + //RtAudio dac( RtAudio::UNSPECIFIED ); + RtAudio *dac = 0; #endif // If you want to change the default sample rate (set in Stk.h), do @@ -254,16 +255,14 @@ int main( int argc, char *argv[] ) // If realtime output, allocate the dac here. #if defined(__STK_REALTIME__) if ( data.realtime ) { + dac = (RtAudio *) new RtAudio( RtAudio::UNSPECIFIED ); RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32; RtAudio::StreamParameters parameters; - parameters.deviceId = dac.getDefaultOutputDevice(); + parameters.deviceId = dac->getDefaultOutputDevice(); parameters.nChannels = data.channels; unsigned int bufferFrames = RT_BUFFER_SIZE; - try { - dac.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data ); - } - catch ( RtAudioError& error ) { - error.printMessage(); + if ( dac->openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data ) ) { + std::cout << dac->getErrorText() << std::endl; goto cleanup; } } @@ -279,11 +278,8 @@ int main( int argc, char *argv[] ) // If realtime output, set our callback function and start the dac. #if defined(__STK_REALTIME__) if ( data.realtime ) { - try { - dac.startStream(); - } - catch ( RtAudioError &error ) { - error.printMessage(); + if ( dac->startStream() ) { + std::cout << dac->getErrorText() << std::endl; goto cleanup; } } @@ -303,14 +299,8 @@ int main( int argc, char *argv[] ) // Shut down the output stream. #if defined(__STK_REALTIME__) - if ( data.realtime ) { - try { - dac.closeStream(); - } - catch ( RtAudioError& error ) { - error.printMessage(); - } - } + if ( data.realtime ) + dac->closeStream(); #endif cleanup: @@ -319,6 +309,7 @@ int main( int argc, char *argv[] ) free( data.wvout ); delete data.voicer; + delete dac; for ( i=0; i @@ -48,6 +48,12 @@ #include #include #include +#include +#include + +#if defined(_WIN32) +#include +#endif // Static variable definitions. const unsigned int RtApi::MAX_SAMPLE_RATES = 14; @@ -56,40 +62,347 @@ const unsigned int RtApi::SAMPLE_RATES[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; -#if defined(_WIN32) || defined(__CYGWIN__) +template inline +std::string convertCharPointerToStdString(const T *text); + +template<> inline +std::string convertCharPointerToStdString(const char *text) +{ + return text; +} + +template<> inline +std::string convertCharPointerToStdString(const wchar_t *text) +{ + return std::wstring_convert>{}.to_bytes(text); +} + +#if defined(_MSC_VER) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) - - #include "tchar.h" - - template inline - std::string convertCharPointerToStdString(const T *text); - - template<> inline - std::string convertCharPointerToStdString(const char *text) - { - return std::string(text); - } - - template<> inline - std::string convertCharPointerToStdString(const wchar_t *text) - { - int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); - std::string s( length-1, '\0' ); - WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL); - return s; - } - -#elif defined(__unix__) || defined(__APPLE__) - // pthread API +#else #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) #endif +// *************************************************** // +// +// RtApi subclass prototypes. +// +// *************************************************** // + +#if defined(__MACOSX_CORE__) + +#include + +class RtApiCore: public RtApi +{ +public: + + RtApiCore(); + ~RtApiCore(); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::MACOSX_CORE; } + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by an internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + bool callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ); + + private: + void probeDevices( void ) override; + bool probeDeviceInfo( AudioDeviceID id, RtAudio::DeviceInfo &info ); + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; + static const char* getErrorCode( OSStatus code ); + std::vector< AudioDeviceID > deviceIds_; +}; + +#endif + +#if defined(__UNIX_JACK__) + +#include + +class RtApiJack: public RtApi +{ +public: + + RtApiJack(); + ~RtApiJack(); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::UNIX_JACK; } + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + bool callbackEvent( unsigned long nframes ); + + private: + void probeDevices( void ) override; + bool probeDeviceInfo( RtAudio::DeviceInfo &info, jack_client_t *client ); + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; + + bool shouldAutoconnect_; +}; + +#endif + +#if defined(__WINDOWS_ASIO__) + +class RtApiAsio: public RtApi +{ +public: + + RtApiAsio(); + ~RtApiAsio(); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_ASIO; } + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + bool callbackEvent( long bufferIndex ); + + private: + + bool coInitialized_; + void probeDevices( void ) override; + bool probeDeviceInfo( RtAudio::DeviceInfo &info ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; +}; + +#endif + +#if defined(__WINDOWS_DS__) + +class RtApiDs: public RtApi +{ +public: + + RtApiDs(); + ~RtApiDs(); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_DS; } + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + void callbackEvent( void ); + + private: + + bool coInitialized_; + bool buffersRolling; + long duplexPrerollBytes; + std::vector dsDevices_; + + void probeDevices( void ) override; + bool probeDeviceInfo( RtAudio::DeviceInfo &info, DsDevice &dsDevice ); + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; +}; + +#endif + +#if defined(__WINDOWS_WASAPI__) + +struct IMMDeviceEnumerator; + +class RtApiWasapi : public RtApi +{ +public: + RtApiWasapi(); + virtual ~RtApiWasapi(); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_WASAPI; } + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + +private: + bool coInitialized_; + IMMDeviceEnumerator* deviceEnumerator_; + std::vector< std::pair< std::string, bool> > deviceIds_; + + void probeDevices( void ) override; + bool probeDeviceInfo( RtAudio::DeviceInfo &info, LPWSTR deviceId, bool isCaptureDevice ); + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ) override; + + 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 +{ +public: + + RtApiAlsa(); + ~RtApiAlsa(); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_ALSA; } + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + void callbackEvent( void ); + + private: + std::vector> deviceIdPairs_; + + void probeDevices( void ) override; + bool probeDeviceInfo( RtAudio::DeviceInfo &info, std::string name ); + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; +}; + +#endif + +#if defined(__LINUX_PULSE__) + +#include + +class RtApiPulse: public RtApi +{ +public: + ~RtApiPulse(); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_PULSE; } + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + void callbackEvent( void ); + + struct PaDeviceInfo { + std::string sinkName; + std::string sourceName; + }; + + private: + std::vector< PaDeviceInfo > paDeviceList_; + + void probeDevices( void ) override; + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; +}; + +#endif + +#if defined(__LINUX_OSS__) + +#include + +class RtApiOss: public RtApi +{ +public: + + RtApiOss(); + ~RtApiOss(); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_OSS; } + void closeStream( void ) override; + RtAudioErrorType startStream( void ) override; + RtAudioErrorType stopStream( void ) override; + RtAudioErrorType abortStream( void ) override; + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesirable results! + void callbackEvent( void ); + + private: + + void probeDevices( void ) override; + bool probeDeviceInfo( RtAudio::DeviceInfo &info, oss_audioinfo &ainfo ); + bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) override; +}; + +#endif + +#if defined(__RTAUDIO_DUMMY__) + +class RtApiDummy: public RtApi +{ +public: + + RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RTAUDIO_WARNING ); } + RtAudio::Api getCurrentApi( void ) override { return RtAudio::RTAUDIO_DUMMY; } + void closeStream( void ) override {} + RtAudioErrorType startStream( void ) override { return RTAUDIO_NO_ERROR; } + RtAudioErrorType stopStream( void ) override { return RTAUDIO_NO_ERROR; } + RtAudioErrorType abortStream( void ) override { return RTAUDIO_NO_ERROR; } + + private: + + bool probeDeviceOpen( unsigned int /*deviceId*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) override { return false; } +}; + +#endif + // *************************************************** // // // RtAudio definitions. @@ -106,31 +419,35 @@ std::string RtAudio :: getVersion( void ) extern "C" { const char* rtaudio_api_names[][2] = { { "unspecified" , "Unknown" }, + { "core" , "CoreAudio" }, { "alsa" , "ALSA" }, + { "jack" , "Jack" }, { "pulse" , "Pulse" }, { "oss" , "OpenSoundSystem" }, - { "jack" , "Jack" }, - { "core" , "CoreAudio" }, - { "wasapi" , "WASAPI" }, { "asio" , "ASIO" }, + { "wasapi" , "WASAPI" }, { "ds" , "DirectSound" }, { "dummy" , "Dummy" }, }; + const unsigned int rtaudio_num_api_names = sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); // The order here will control the order of RtAudio's API search in // the constructor. extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { +#if defined(__MACOSX_CORE__) + RtAudio::MACOSX_CORE, +#endif +#if defined(__LINUX_ALSA__) + RtAudio::LINUX_ALSA, +#endif #if defined(__UNIX_JACK__) RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) RtAudio::LINUX_PULSE, #endif -#if defined(__LINUX_ALSA__) - RtAudio::LINUX_ALSA, -#endif #if defined(__LINUX_OSS__) RtAudio::LINUX_OSS, #endif @@ -143,14 +460,12 @@ extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { #if defined(__WINDOWS_DS__) RtAudio::WINDOWS_DS, #endif -#if defined(__MACOSX_CORE__) - RtAudio::MACOSX_CORE, -#endif #if defined(__RTAUDIO_DUMMY__) RtAudio::RTAUDIO_DUMMY, #endif RtAudio::UNSPECIFIED, }; + extern "C" const unsigned int rtaudio_num_compiled_apis = sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; } @@ -192,6 +507,15 @@ RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) return RtAudio::UNSPECIFIED; } +RtAudio::Api RtAudio :: getCompiledApiByDisplayName( const std::string &name ) +{ + unsigned int i=0; + for (i = 0; i < rtaudio_num_compiled_apis; ++i) + if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][1]) + return rtaudio_compiled_apis[i]; + return RtAudio::UNSPECIFIED; +} + void RtAudio :: openRtApi( RtAudio::Api api ) { if ( rtapi_ ) @@ -236,18 +560,27 @@ void RtAudio :: openRtApi( RtAudio::Api api ) #endif } -RtAudio :: RtAudio( RtAudio::Api api ) +RtAudio :: RtAudio( RtAudio::Api api, RtAudioErrorCallback&& errorCallback ) { rtapi_ = 0; + std::string errorMessage; if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openRtApi( api ); - if ( rtapi_ ) return; - // No compiled support for specified API value. Issue a debug - // warning and continue as if no API was specified. - std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; + if ( rtapi_ ) { + if ( errorCallback ) rtapi_->setErrorCallback( errorCallback ); + return; + } + + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + errorMessage = "RtAudio: no compiled support for specified API argument!"; + if ( errorCallback ) + errorCallback( RTAUDIO_INVALID_USE, errorMessage ); + else + std::cerr << '\n' << errorMessage << '\n' << std::endl; } // Iterate through the compiled APIs and return as soon as we find @@ -256,17 +589,25 @@ RtAudio :: RtAudio( RtAudio::Api api ) getCompiledApi( apis ); for ( unsigned int i=0; igetDeviceCount() ) break; + if ( rtapi_ && (rtapi_->getDeviceNames()).size() > 0 ) + break; } - if ( rtapi_ ) return; + if ( rtapi_ ) { + if ( errorCallback ) rtapi_->setErrorCallback( errorCallback ); + return; + } // 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 throw an error. - std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; - throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); + // definition __RTAUDIO_DUMMY__ is automatically defined in RtAudio.h + // if no API-specific definitions are passed to the compiler. But just + // in case something weird happens, issue an error message and abort. + errorMessage = "RtAudio: no compiled API support found ... critical error!"; + if ( errorCallback ) + errorCallback( RTAUDIO_INVALID_USE, errorMessage ); + else + std::cerr << '\n' << errorMessage << '\n' << std::endl; + abort(); } RtAudio :: ~RtAudio() @@ -275,17 +616,16 @@ RtAudio :: ~RtAudio() delete rtapi_; } -void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, - RtAudio::StreamParameters *inputParameters, - RtAudioFormat format, unsigned int sampleRate, - unsigned int *bufferFrames, - RtAudioCallback callback, void *userData, - RtAudio::StreamOptions *options, - RtAudioErrorCallback errorCallback ) +RtAudioErrorType RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, - userData, options, errorCallback ); + userData, options ); } // *************************************************** // @@ -297,14 +637,11 @@ void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtApi :: RtApi() { - stream_.state = STREAM_CLOSED; - stream_.mode = UNINITIALIZED; - stream_.apiHandle = 0; - stream_.userBuffer[0] = 0; - stream_.userBuffer[1] = 0; + clearStreamInfo(); MUTEX_INITIALIZE( &stream_.mutex ); + errorCallback_ = 0; showWarnings_ = true; - firstErrorOccurred_ = false; + currentDeviceId_ = 129; } RtApi :: ~RtApi() @@ -312,18 +649,16 @@ RtApi :: ~RtApi() MUTEX_DESTROY( &stream_.mutex ); } -void RtApi :: openStream( RtAudio::StreamParameters *oParams, - RtAudio::StreamParameters *iParams, - RtAudioFormat format, unsigned int sampleRate, - unsigned int *bufferFrames, - RtAudioCallback callback, void *userData, - RtAudio::StreamOptions *options, - RtAudioErrorCallback errorCallback ) +RtAudioErrorType RtApi :: openStream( RtAudio::StreamParameters *oParams, + RtAudio::StreamParameters *iParams, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; - error( RtAudioError::INVALID_USE ); - return; + return error( RTAUDIO_INVALID_USE ); } // Clear stream information potentially left from a previously open stream. @@ -331,46 +666,49 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; - error( RtAudioError::INVALID_USE ); - return; + return error( RTAUDIO_INVALID_PARAMETER ); } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; - error( RtAudioError::INVALID_USE ); - return; + return error( RTAUDIO_INVALID_PARAMETER ); } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; - error( RtAudioError::INVALID_USE ); - return; + return error( RTAUDIO_INVALID_PARAMETER ); } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; - error( RtAudioError::INVALID_USE ); - return; + return error( RTAUDIO_INVALID_PARAMETER ); } - unsigned int nDevices = getDeviceCount(); - unsigned int oChannels = 0; + // Scan devices if none currently listed. + if ( deviceList_.size() == 0 ) probeDevices(); + + unsigned int m, oChannels = 0; if ( oParams ) { oChannels = oParams->nChannels; - if ( oParams->deviceId >= nDevices ) { - errorText_ = "RtApi::openStream: output device parameter value is invalid."; - error( RtAudioError::INVALID_USE ); - return; + // Verify that the oParams->deviceId is found in our list + for ( m=0; mdeviceId ) break; + } + if ( m == deviceList_.size() ) { + errorText_ = "RtApi::openStream: output device ID is invalid."; + return error( RTAUDIO_INVALID_PARAMETER ); } } unsigned int iChannels = 0; if ( iParams ) { iChannels = iParams->nChannels; - if ( iParams->deviceId >= nDevices ) { - errorText_ = "RtApi::openStream: input device parameter value is invalid."; - error( RtAudioError::INVALID_USE ); - return; + for ( m=0; mdeviceId ) break; + } + if ( m == deviceList_.size() ) { + errorText_ = "RtApi::openStream: input device ID is invalid."; + return error( RTAUDIO_INVALID_PARAMETER ); } } @@ -380,38 +718,95 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); - if ( result == false ) { - error( RtAudioError::SYSTEM_ERROR ); - return; - } + if ( result == false ) + return error( RTAUDIO_SYSTEM_ERROR ); } if ( iChannels > 0 ) { result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, sampleRate, format, bufferFrames, options ); - if ( result == false ) { - if ( oChannels > 0 ) closeStream(); - error( RtAudioError::SYSTEM_ERROR ); - return; - } + if ( result == false ) + return error( RTAUDIO_SYSTEM_ERROR ); } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; - stream_.callbackInfo.errorCallback = (void *) errorCallback; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; + return RTAUDIO_NO_ERROR; +} + +void RtApi :: probeDevices( void ) +{ + // This function MUST be implemented in all subclasses! Within each + // API, this function will be used to: + // - enumerate the devices and fill or update our + // std::vector< RtAudio::DeviceInfo> deviceList_ class variable + // - store corresponding (usually API-specific) identifiers that + // are needed to open each device + // - make sure that the default devices are properly identified + // within the deviceList_ (unless API-specific functions are + // available for this purpose). + // + // The function should not reprobe devices that have already been + // found. The function must properly handle devices that are removed + // or added. + // + // Ideally, we would also configure callback functions to be invoked + // when devices are added or removed (which could be used to inform + // clients about changes). However, none of the APIs currently + // support notification of _new_ devices and I don't see the + // usefulness of having this work only for device removal. + return; +} + +unsigned int RtApi :: getDeviceCount( void ) +{ + probeDevices(); + return (unsigned int)deviceList_.size(); +} + +std::vector RtApi :: getDeviceIds( void ) +{ + probeDevices(); + + // Copy device IDs into output vector. + std::vector deviceIds; + for ( unsigned int m=0; m RtApi :: getDeviceNames( void ) +{ + probeDevices(); + + // Copy device names into output vector. + std::vector deviceNames; + for ( unsigned int m=0; m 0 ) { + deviceList_[i].isDefaultInput = true; + return deviceList_[i].ID; } } @@ -421,23 +816,44 @@ unsigned int RtApi :: getDefaultInputDevice( void ) unsigned int RtApi :: getDefaultOutputDevice( void ) { // Should be reimplemented in subclasses if necessary. - unsigned int nDevices = getDeviceCount(); - for ( unsigned int i = 0; i < nDevices; i++ ) { - if ( getDeviceInfo( i ).isDefaultOutput ) { - return i; + if ( deviceList_.size() == 0 ) probeDevices(); + for ( unsigned int i = 0; i < deviceList_.size(); i++ ) { + if ( deviceList_[i].isDefaultOutput ) + return deviceList_[i].ID; + } + + // If not found, find the first device with output channels, set it + // as the default, and return the ID. + for ( unsigned int i = 0; i < deviceList_.size(); i++ ) { + if ( deviceList_[i].outputChannels > 0 ) { + deviceList_[i].isDefaultOutput = true; + return deviceList_[i].ID; } } return 0; } +RtAudio::DeviceInfo RtApi :: getDeviceInfo( unsigned int deviceId ) +{ + if ( deviceList_.size() == 0 ) probeDevices(); + for ( unsigned int m=0; m= 0.0 ) stream_.streamTime = time; + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif + */ } unsigned int RtApi :: getStreamSampleRate( void ) { - verifyStream(); - - return stream_.sampleRate; + if ( isStreamOpen() ) return stream_.sampleRate; + else return 0; } @@ -553,11 +968,19 @@ struct CoreHandle { pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. + bool xrunListenerAdded[2]; + bool disconnectListenerAdded[2]; 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; } + :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; procId[0] = 0; procId[1] = 0; xrun[0] = false; xrun[1] = false; xrunListenerAdded[0] = false; xrunListenerAdded[1] = false; disconnectListenerAdded[0] = false; disconnectListenerAdded[1] = false; } }; +#if defined( MAC_OS_VERSION_12_0 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0 ) + #define KAUDIOOBJECTPROPERTYELEMENT kAudioObjectPropertyElementMain +#else + #define KAUDIOOBJECTPROPERTYELEMENT kAudioObjectPropertyElementMaster // deprecated with macOS 12 +#endif + RtApiCore:: RtApiCore() { #if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) @@ -568,11 +991,11 @@ RtApiCore:: RtApiCore() CFRunLoopRef theRunLoop = NULL; AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; + KAUDIOOBJECTPROPERTYELEMENT }; OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_SYSTEM_ERROR ); } #endif } @@ -585,136 +1008,205 @@ RtApiCore :: ~RtApiCore() if ( stream_.state != STREAM_CLOSED ) closeStream(); } -unsigned int RtApiCore :: getDeviceCount( void ) +unsigned int RtApiCore :: getDefaultOutputDevice( void ) { - // Find out how many audio devices there are, if any. - UInt32 dataSize; - AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); + AudioDeviceID id; + UInt32 dataSize = sizeof( AudioDeviceID ); + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, KAUDIOOBJECTPROPERTYELEMENT }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { - errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; - error( RtAudioError::WARNING ); + errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device."; + error( RTAUDIO_SYSTEM_ERROR ); return 0; } - return dataSize / sizeof( AudioDeviceID ); + for ( unsigned int m=0; mobject; + info->deviceDisconnected = true; + object->closeStream(); + return kAudioHardwareUnspecifiedError; + } } - - dataSize = sizeof( AudioDeviceID ) * nDevices; - AudioDeviceID deviceList[ nDevices ]; - property.mSelector = kAudioHardwarePropertyDevices; - result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); - if ( result != noErr ) { - errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device IDs."; - error( RtAudioError::WARNING ); - return 0; - } - - for ( unsigned int i=0; i= nDevices ) { - errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; - error( RtAudioError::INVALID_USE ); - return info; - } - - AudioDeviceID deviceList[ nDevices ]; - UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; - AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, - 0, NULL, &dataSize, (void *) &deviceList ); + AudioDeviceID ids[ nDevices ]; + property.mSelector = kAudioHardwarePropertyDevices; + result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &ids ); if ( result != noErr ) { - errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; - error( RtAudioError::WARNING ); - return info; + errorText_ = "RtApiCore::probeDevices: OS-X system error getting device IDs."; + error( RTAUDIO_SYSTEM_ERROR ); + return; } - AudioDeviceID id = deviceList[ device ]; + // Fill or update the deviceList_ and also save a corresponding list of Ids. + for ( unsigned int n=0; n::iterator it=deviceIds_.begin(); it!=deviceIds_.end(); ) { + for ( m=0; m 0 ) - if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; - if ( info.inputChannels > 0 ) - if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; - - info.probed = true; - return info; + return true; } static OSStatus callbackHandler( AudioDeviceID inDevice, @@ -916,6 +1407,8 @@ static OSStatus callbackHandler( AudioDeviceID inDevice, void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; + if(info == NULL || info->object == NULL) + return kAudioHardwareUnspecifiedError; RtApiCore *object = (RtApiCore *) info->object; if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) @@ -942,69 +1435,40 @@ static OSStatus xrunListener( AudioObjectID /*inDevice*/, return kAudioHardwareNoError; } -static OSStatus rateListener( AudioObjectID inDevice, - UInt32 /*nAddresses*/, - const AudioObjectPropertyAddress /*properties*/[], - void* ratePointer ) -{ - Float64 *rate = (Float64 *) ratePointer; - UInt32 dataSize = sizeof( Float64 ); - AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); - return kAudioHardwareNoError; -} - -bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, +bool RtApiCore :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { - // Get device ID - unsigned int nDevices = getDeviceCount(); - if ( nDevices == 0 ) { - // This should not happen because a check is made before this function is called. - errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; + AudioDeviceID id = 0; + for ( unsigned int m=0; m= nDevices ) { - // This should not happen because a check is made before this function is called. - errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; - return FAILURE; - } - - AudioDeviceID deviceList[ nDevices ]; - UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, - 0, NULL, &dataSize, (void *) &deviceList ); - if ( result != noErr ) { - errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; - return FAILURE; - } - - AudioDeviceID id = deviceList[ device ]; + kAudioDevicePropertyScopeOutput, + KAUDIOOBJECTPROPERTYELEMENT }; // Setup for stream mode. - bool isInput = false; if ( mode == INPUT ) { - isInput = true; property.mScope = kAudioDevicePropertyScopeInput; } - else - property.mScope = kAudioDevicePropertyScopeOutput; // Get the stream "configuration". AudioBufferList *bufferList = nil; - dataSize = 0; + UInt32 dataSize = 0; property.mSelector = kAudioDevicePropertyStreamConfiguration; - result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + OSStatus result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1019,7 +1483,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1045,13 +1509,13 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( deviceChannels < ( channels + firstChannel ) ) { free( bufferList ); - errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; + errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << deviceId << ") does not support the requested channel count."; errorText_ = errorStream_.str(); return FAILURE; } // Look for a single stream meeting our needs. - UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; + UInt32 firstStream = 0, streamCount = 1, streamChannels = 0, channelOffset = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels >= channels + offsetCounter ) { @@ -1097,14 +1561,14 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } - if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; - else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; - if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; + if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned int) bufferRange.mMinimum; + else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned int) bufferRange.mMaximum; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned int) bufferRange.mMinimum; // Set the buffer size. For multiple streams, I'm assuming we only // need to make this setting for the master channel. @@ -1114,7 +1578,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1123,7 +1587,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // MUST be the same in both directions! *bufferSize = theSize; if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1165,41 +1629,29 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne return FAILURE; } - // Only change the sample rate if off by more than 1 Hz. + // Only try to change the sample rate if off by more than 1 Hz. if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { - // Set a property listener for the sample rate change - Float64 reportedRate = 0.0; - AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); - if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; - errorText_ = errorStream_.str(); - return FAILURE; - } - nominalRate = (Float64) sampleRate; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); if ( result != noErr ) { - AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Now wait until the reported nominal rate is what we just set. UInt32 microCounter = 0; + Float64 reportedRate = 0.0; while ( reportedRate != nominalRate ) { microCounter += 5000; - if ( microCounter > 5000000 ) break; + if ( microCounter > 2000000 ) break; usleep( 5000 ); + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &reportedRate ); } - // Remove the property listener. - AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); - - if ( microCounter > 5000000 ) { - errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; + if ( microCounter > 2000000 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1212,7 +1664,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne property.mSelector = kAudioStreamPropertyVirtualFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1234,7 +1686,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( updateFormat ) { result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1244,7 +1696,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne property.mSelector = kAudioStreamPropertyPhysicalFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1299,7 +1751,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } if ( !setPhysicalFormat ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting physical data format for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting physical data format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -1313,9 +1765,9 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &latency ); if ( result == kAudioHardwareNoError ) stream_.latency[ mode ] = latency; else { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting device latency for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting device latency for device (" << deviceId << ")."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); } } @@ -1380,9 +1832,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); - // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); - stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); - memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; goto error; @@ -1414,7 +1864,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } stream_.sampleRate = sampleRate; - stream_.device[mode] = device; + stream_.deviceId[mode] = deviceId; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; @@ -1424,8 +1874,8 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne else setConvertInfo( mode, channelOffset ); } - if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) - // Only one callback procedure per device. + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.deviceId[0] == deviceId ) + // Only one callback procedure and property listener per device. stream_.mode = DUPLEX; else { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) @@ -1435,7 +1885,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); #endif if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << deviceId << ")."; errorText_ = errorStream_.str(); goto error; } @@ -1443,35 +1893,34 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.mode = DUPLEX; else stream_.mode = mode; - } - // Setup the device property listener for over/underload. - property.mSelector = kAudioDeviceProcessorOverload; - property.mScope = kAudioObjectPropertyScopeGlobal; - result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); + // Setup the device property listener for over/underload. + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting xrun listener for device (" << deviceId << ")."; + errorText_ = errorStream_.str(); + goto error; + } + handle->xrunListenerAdded[mode] = true; + + // Setup a listener to detect a possible device disconnect. + property.mSelector = kAudioDevicePropertyDeviceIsAlive; + result = AudioObjectAddPropertyListener( id , &property, streamDisconnectListener, (void *) &stream_.callbackInfo ); + if ( result != noErr ) { + AudioObjectRemovePropertyListener( id, &property, xrunListener, (void *) handle ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting disconnect listener for device (" << deviceId << ")."; + errorText_ = errorStream_.str(); + goto error; + } + handle->disconnectListenerAdded[mode] = true; + } return SUCCESS; error: - if ( handle ) { - pthread_cond_destroy( &handle->condition ); - delete handle; - 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; - } - - stream_.state = STREAM_CLOSED; + closeStream(); // this should safely clear out procedures, listeners and memory, even for duplex stream return FAILURE; } @@ -1479,28 +1928,37 @@ void RtApiCore :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::closeStream(): no open stream to close!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { - if (handle) { + if ( handle ) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - property.mSelector = kAudioDeviceProcessorOverload; - property.mScope = kAudioObjectPropertyScopeGlobal; - if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { - errorText_ = "RtApiCore::closeStream(): error removing property listener!"; - error( RtAudioError::WARNING ); + kAudioObjectPropertyScopeGlobal, + KAUDIOOBJECTPROPERTYELEMENT }; + if ( handle->xrunListenerAdded[0] ) { + property.mSelector = kAudioDeviceProcessorOverload; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; + error( RTAUDIO_WARNING ); + } + } + if ( handle->disconnectListenerAdded[0] ) { + property.mSelector = kAudioDevicePropertyDeviceIsAlive; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, streamDisconnectListener, (void *) &stream_.callbackInfo ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; + error( RTAUDIO_WARNING ); + } } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[0], handle->procId[0] ); - AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); + if ( handle->procId[0] ) { + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], handle->procId[0] ); + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); + } #else // deprecated behaviour if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], callbackHandler ); @@ -1509,23 +1967,34 @@ void RtApiCore :: closeStream( void ) } } - if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { - if (handle) { + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.deviceId[0] != stream_.deviceId[1] ) ) { + if ( handle ) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; + KAUDIOOBJECTPROPERTYELEMENT }; - property.mSelector = kAudioDeviceProcessorOverload; - property.mScope = kAudioObjectPropertyScopeGlobal; - if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { - errorText_ = "RtApiCore::closeStream(): error removing property listener!"; - error( RtAudioError::WARNING ); + if ( handle->xrunListenerAdded[1] ) { + property.mSelector = kAudioDeviceProcessorOverload; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; + error( RTAUDIO_WARNING ); + } + } + + if ( handle->disconnectListenerAdded[0] ) { + property.mSelector = kAudioDevicePropertyDeviceIsAlive; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, streamDisconnectListener, (void *) &stream_.callbackInfo ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; + error( RTAUDIO_WARNING ); + } } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[1], handle->procId[1] ); - AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); + if ( handle->procId[1] ) { + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], handle->procId[1] ); + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); + } #else // deprecated behaviour if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], callbackHandler ); @@ -1547,26 +2016,35 @@ void RtApiCore :: closeStream( void ) } // Destroy pthread condition variable. + pthread_cond_signal( &handle->condition ); // signal condition variable in case stopStream is blocked pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; - stream_.mode = UNINITIALIZED; - stream_.state = STREAM_CLOSED; + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + if ( info->deviceDisconnected ) { + errorText_ = "RtApiCore: the stream device was disconnected (and closed)!"; + error( RTAUDIO_DEVICE_DISCONNECT ); + } + + clearStreamInfo(); } -void RtApiCore :: startStream( void ) +RtAudioErrorType RtApiCore :: startStream( void ) { - verifyStream(); - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiCore::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiCore::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiCore::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } -#if defined( HAVE_GETTIMEOFDAY ) + /* + #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); -#endif + #endif + */ OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; @@ -1578,14 +2056,19 @@ void RtApiCore :: startStream( void ) result = AudioDeviceStart( handle->id[0], callbackHandler ); #endif if ( result != noErr ) { - errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; + errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || - ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + ( stream_.mode == DUPLEX && stream_.deviceId[0] != stream_.deviceId[1] ) ) { + + // Clear user input buffer + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[1] * stream_.bufferSize * formatBytes( stream_.userFormat ); + memset( stream_.userBuffer[1], 0, bufferBytes * sizeof(char) ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStart( handle->id[1], handle->procId[1] ); @@ -1593,7 +2076,7 @@ void RtApiCore :: startStream( void ) result = AudioDeviceStart( handle->id[1], callbackHandler ); #endif if ( result != noErr ) { - errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; + errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.deviceId[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } @@ -1604,17 +2087,18 @@ void RtApiCore :: startStream( void ) stream_.state = STREAM_RUNNING; unlock: - if ( result == noErr ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result == noErr ) return RTAUDIO_NO_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } -void RtApiCore :: stopStream( void ) +RtAudioErrorType RtApiCore :: stopStream( void ) { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiCore::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); } OSStatus result = noErr; @@ -1632,21 +2116,20 @@ void RtApiCore :: stopStream( void ) result = AudioDeviceStop( handle->id[0], callbackHandler ); #endif if ( result != noErr ) { - errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } - if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { - + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.deviceId[0] != stream_.deviceId[1] ) ) { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStop( handle->id[1], handle->procId[1] ); #else // deprecated behaviour result = AudioDeviceStop( handle->id[1], callbackHandler ); #endif if ( result != noErr ) { - errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.deviceId[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } @@ -1655,30 +2138,32 @@ void RtApiCore :: stopStream( void ) stream_.state = STREAM_STOPPED; unlock: - if ( result == noErr ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result == noErr ) return RTAUDIO_NO_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } -void RtApiCore :: abortStream( void ) +RtAudioErrorType RtApiCore :: abortStream( void ) { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiCore::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; handle->drainCounter = 2; - stopStream(); + stream_.state = STREAM_STOPPING; + return stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is better to handle it this way because the -// callbackEvent() function probably should return before the AudioDeviceStop() -// function is called. +// callbackEvent() function probably should return before the +// AudioDeviceStop() function is called. static void *coreStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; @@ -1695,7 +2180,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( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return FAILURE; } @@ -1735,8 +2220,6 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { - stream_.state = STREAM_STOPPING; - handle->drainCounter = 2; abortStream(); return SUCCESS; } @@ -1942,11 +2425,15 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, } unlock: - //MUTEX_UNLOCK( &stream_.mutex ); // Make sure to only tick duplex stream time once if using two devices - if ( stream_.mode != DUPLEX || (stream_.mode == DUPLEX && handle->id[0] != handle->id[1] && deviceId == handle->id[0] ) ) - RtApi::tickStreamTime(); + if ( stream_.mode == DUPLEX ) { + if ( handle->id[0] == handle->id[1] ) // same device, only one callback + RtApi::tickStreamTime(); + else if ( deviceId == handle->id[0] ) + RtApi::tickStreamTime(); // two devices, only tick on the output callback + } else + RtApi::tickStreamTime(); // input or output stream only return SUCCESS; } @@ -1999,13 +2486,15 @@ const char* RtApiCore :: getErrorCode( OSStatus code ) #if defined(__UNIX_JACK__) // JACK is a low-latency audio server, originally written for the -// GNU/Linux operating system and now also ported to OS-X. It can -// connect a number of different applications to an audio device, as -// well as allowing them to share audio between themselves. +// GNU/Linux operating system and now also ported to OS-X and +// Windows. It can connect a number of different applications to an +// audio device, as well as allowing them to share audio between +// themselves. // // When using JACK with RtAudio, "devices" refer to JACK clients that -// have ports connected to the server. The JACK server is typically -// started in a terminal as follows: +// have ports connected to the server, while ports correspond to device +// channels. The JACK server is typically started in a terminal as +// follows: // // .jackd -d alsa -d hw:0 // @@ -2027,7 +2516,6 @@ const char* RtApiCore :: getErrorCode( OSStatus code ) // devices are available (i.e., the JACK server is not running), a // stream cannot be opened. -#include #include #include @@ -2046,6 +2534,20 @@ struct JackHandle { :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; +std::string escapeJackPortRegex(std::string &str) +{ + const std::string need_escaping = "()[]{}*+?$^.|\\"; + std::string escaped_string; + for (auto c : str) + { + if (need_escaping.find(c) != std::string::npos) + escaped_string.push_back('\\'); + + escaped_string.push_back(c); + } + return escaped_string; +} + #if !defined(__RTAUDIO_DEBUG__) static void jackSilentError( const char * ) {}; #endif @@ -2064,17 +2566,26 @@ RtApiJack :: ~RtApiJack() if ( stream_.state != STREAM_CLOSED ) closeStream(); } -unsigned int RtApiJack :: getDeviceCount( void ) +void RtApiJack :: probeDevices( void ) { + // See list of required functionality in RtApi::probeDevices(). + // See if we can become a jack client. jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; - jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); - if ( client == 0 ) return 0; + jack_client_t *client = jack_client_open( "RtApiJackProbe", options, status ); + if ( client == 0 ) { + deviceList_.clear(); // in case the server is shutdown after a previous successful probe + errorText_ = "RtApiJack::probeDevices: Jack server not found or connection error!"; + //error( RTAUDIO_SYSTEM_ERROR ); + error( RTAUDIO_WARNING ); + return; + } const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; + std::vector portNames; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). @@ -2083,8 +2594,9 @@ unsigned int RtApiJack :: getDeviceCount( void ) port = (char *) ports[ nChannels ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { - port = port.substr( 0, iColon + 1 ); + port = port.substr( 0, iColon ); if ( port != previousPort ) { + portNames.push_back( port ); nDevices++; previousPort = port; } @@ -2093,53 +2605,55 @@ unsigned int RtApiJack :: getDeviceCount( void ) free( ports ); } + // Fill or update the deviceList_. + unsigned int m, n; + for ( n=0; n::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { + for ( m=0; m= nDevices ) { - jack_client_close( client ); - errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; - error( RtAudioError::INVALID_USE ); - return info; - } - // Get the current jack server sample rate. info.sampleRates.clear(); @@ -2149,7 +2663,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; - ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); + const char **ports = jack_get_ports( client, escapeJackPortRegex(info.name).c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2158,7 +2672,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Jack "output ports" equal RtAudio input channels. nChannels = 0; - ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); + ports = jack_get_ports( client, escapeJackPortRegex(info.name).c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2168,8 +2682,8 @@ 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( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } // If device opens for both playback and capture, we determine the channels. @@ -2179,15 +2693,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Jack always uses 32-bit floats. info.nativeFormats = RTAUDIO_FLOAT32; - // Jack doesn't provide default devices so we'll use the first available one. - if ( device == 0 && info.outputChannels > 0 ) - info.isDefaultOutput = true; - if ( device == 0 && info.inputChannels > 0 ) - info.isDefaultInput = true; - - jack_client_close(client); - info.probed = true; - return info; + return true; } static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) @@ -2209,10 +2715,19 @@ static void *jackCloseStream( void *ptr ) CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; + info->deviceDisconnected = true; object->closeStream(); - pthread_exit( NULL ); } + +/* +// Could be used to catch client connections but requires open client. +static void jackClientChange( const char *name, int registered, void *infoPointer ) +{ + std::cout << "in jackClientChange, name = " << name << ", registered = " << registered << std::endl; +} +*/ + static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; @@ -2227,7 +2742,6 @@ static void jackShutdown( void *infoPointer ) 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; } static int jackXrun( void *infoPointer ) @@ -2240,7 +2754,7 @@ static int jackXrun( void *infoPointer ) return 0; } -bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, +bool RtApiJack :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) @@ -2258,7 +2772,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( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return FAILURE; } } @@ -2267,29 +2781,15 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne client = handle->client; } - const char **ports; - std::string port, previousPort, deviceName; - unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); - if ( ports ) { - // Parse the port names up to the first colon (:). - size_t iColon = 0; - do { - port = (char *) ports[ nPorts ]; - iColon = port.find(":"); - if ( iColon != std::string::npos ) { - port = port.substr( 0, iColon ); - if ( port != previousPort ) { - if ( nDevices == device ) deviceName = port; - nDevices++; - previousPort = port; - } - } - } while ( ports[++nPorts] ); - free( ports ); + std::string deviceName; + for ( unsigned int m=0; m= nDevices ) { + if ( deviceName.empty() ) { errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; return FAILURE; } @@ -2297,18 +2797,19 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; + const char **ports; if ( ! (options && (options->flags & RTAUDIO_JACK_DONT_CONNECT)) ) { // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; - ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); + ports = jack_get_ports( client, escapeJackPortRegex(deviceName).c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); } // Compare the jack ports for specified client to the requested number of channels. if ( nChannels < (channels + firstChannel) ) { - errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; + errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << deviceName << ")."; errorText_ = errorStream_.str(); return FAILURE; } @@ -2325,7 +2826,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.sampleRate = jackRate; // Get the latency of the JACK port. - ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); + ports = jack_get_ports( client, escapeJackPortRegex(deviceName).c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); @@ -2427,7 +2928,6 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne goto error; } - stream_.device[mode] = device; stream_.channelOffset[mode] = firstChannel; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; @@ -2440,6 +2940,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); + //jack_set_client_registration_callback( handle->client, jackClientChange, (void *) &stream_.callbackInfo ); } // Register our ports. @@ -2499,20 +3000,25 @@ void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( handle ) { - if ( stream_.state == STREAM_RUNNING ) jack_deactivate( handle->client ); + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + for ( unsigned int i=0; iclient, handle->ports[0][i] ); + } + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + for ( unsigned int i=0; iclient, handle->ports[1][i] ); + } jack_client_close( handle->client ); - } - - if ( handle ) { + if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); pthread_cond_destroy( &handle->condition ); @@ -2520,6 +3026,12 @@ void RtApiJack :: closeStream( void ) stream_.apiHandle = 0; } + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + if ( info->deviceDisconnected ) { + errorText_ = "RtApiJack: the Jack server is shutting down this client ... stream stopped and closed!"; + error( RTAUDIO_DEVICE_DISCONNECT ); + } + for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); @@ -2532,22 +3044,24 @@ void RtApiJack :: closeStream( void ) stream_.deviceBuffer = 0; } - stream_.mode = UNINITIALIZED; - stream_.state = STREAM_CLOSED; + clearStreamInfo(); } -void RtApiJack :: startStream( void ) +RtAudioErrorType RtApiJack :: startStream( void ) { - verifyStream(); - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiJack::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiJack::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiJack::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif + */ JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); @@ -2560,8 +3074,7 @@ void RtApiJack :: startStream( void ) // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { - result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); + ports = jack_get_ports( handle->client, escapeJackPortRegex(handle->deviceName[0]).c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; @@ -2584,8 +3097,7 @@ void RtApiJack :: startStream( void ) } if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { - result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); + ports = jack_get_ports( handle->client, escapeJackPortRegex(handle->deviceName[1]).c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; @@ -2610,17 +3122,18 @@ void RtApiJack :: startStream( void ) stream_.state = STREAM_RUNNING; unlock: - if ( result == 0 ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result == 0 ) return RTAUDIO_NO_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } -void RtApiJack :: stopStream( void ) +RtAudioErrorType RtApiJack :: stopStream( void ) { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiJack::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); } JackHandle *handle = (JackHandle *) stream_.apiHandle; @@ -2634,21 +3147,23 @@ void RtApiJack :: stopStream( void ) jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; + return RTAUDIO_NO_ERROR; } -void RtApiJack :: abortStream( void ) +RtAudioErrorType RtApiJack :: abortStream( void ) { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiJack::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } JackHandle *handle = (JackHandle *) stream_.apiHandle; handle->drainCounter = 2; - stopStream(); + return stopStream(); } // This function will be called by a spawned thread when the user @@ -2669,13 +3184,13 @@ 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( RtAudioError::WARNING ); + errorText_ = "RtApiJack::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RTAUDIO_WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { - errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; - error( RtAudioError::WARNING ); + errorText_ = "RtApiJack::callbackEvent(): the JACK buffer size has changed ... cannot process!"; + error( RTAUDIO_WARNING ); return FAILURE; } @@ -2689,7 +3204,7 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); - else + else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } @@ -2784,7 +3299,7 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) #if defined(__WINDOWS_ASIO__) // ASIO API on Windows // The ASIO API is designed around a callback scheme, so this -// implementation is similar to that used for OS-X CoreAudio and Linux +// implementation is similar to that used for OS-X CoreAudio and unix // Jack. The primary constraint with ASIO is that it only allows // access to a single driver at a time. Thus, it is not possible to // have more than one simultaneous RtAudio stream. @@ -2810,6 +3325,7 @@ static ASIOCallbacks asioCallbacks; static ASIODriverInfo driverInfo; static CallbackInfo *asioCallbackInfo; static bool asioXRun; +static bool streamOpen = false; // Tracks whether any instance of RtAudio has a stream open struct AsioHandle { int drainCounter; // Tracks callback counts when draining @@ -2835,11 +3351,18 @@ RtApiAsio :: RtApiAsio() HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded apartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); } coInitialized_ = true; - drivers.removeCurrentDriver(); + // Check whether another RtAudio instance has an ASIO stream open. + if ( streamOpen ) { + errorText_ = "RtApiAsio(): Another RtAudio ASIO stream is open, functionality may be limited."; + error( RTAUDIO_WARNING ); + } + else + drivers.removeCurrentDriver(); + driverInfo.asioVersion = 2; // See note in DirectSound implementation about GetDesktopWindow(). @@ -2852,87 +3375,98 @@ RtApiAsio :: ~RtApiAsio() if ( coInitialized_ ) CoUninitialize(); } -unsigned int RtApiAsio :: getDeviceCount( void ) +void RtApiAsio :: probeDevices( void ) { - return (unsigned int) drivers.asioGetNumDev(); -} + // See list of required functionality in RtApi::probeDevices(). -// We can only load one ASIO driver, so the default output is always the first device. -unsigned int RtApiAsio :: getDefaultOutputDevice( void ) -{ - return 0; -} - -// We can only load one ASIO driver, so the default input is always the first device. -unsigned int RtApiAsio :: getDefaultInputDevice( void ) -{ - return 0; -} - -RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) -{ - RtAudio::DeviceInfo info; - info.probed = false; - - // Get device ID - unsigned int nDevices = getDeviceCount(); + if ( streamOpen ) { + errorText_ = "RtApiAsio::probeDevices: Another RtAudio ASIO stream is open, cannot probe devices."; + error( RTAUDIO_WARNING ); + return; + } + + unsigned int nDevices = drivers.asioGetNumDev(); if ( nDevices == 0 ) { - errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; - error( RtAudioError::INVALID_USE ); - return info; + deviceList_.clear(); + return; } - if ( device >= nDevices ) { - errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; - 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( RtAudioError::WARNING ); - return info; + char tmp[32]; + std::vector< std::string > driverNames; + unsigned int n, m; + for ( n=0; n::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { + for ( m=0; m 0) + { + getDefaultInputDevice(); + getDefaultOutputDevice(); + } +} + +bool RtApiAsio :: probeDeviceInfo( RtAudio::DeviceInfo &info ) +{ + if ( !drivers.loadDriver( const_cast(info.name.c_str()) ) ) { + errorStream_ << "RtApiAsio::probeDeviceInfo: unable to load driver (" << info.name << ")."; + errorText_ = errorStream_.str(); + error( RTAUDIO_WARNING ); + return false; + } + + ASIOError result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { - errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::probeDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << info.name << ")."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; - } - - info.name = driverName; - - if ( !drivers.loadDriver( driverName ) ) { - errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; - errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; - } - - result = ASIOInit( &driverInfo ); - if ( result != ASE_OK ) { - errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; - errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } // Determine the device channel information. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { + ASIOExit(); drivers.removeCurrentDriver(); - errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorStream_ << "RtApiAsio::probeDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << info.name << ")."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } info.outputChannels = outputChannels; @@ -2959,11 +3493,12 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( info.inputChannels <= 0 ) channelInfo.isInput = false; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { + ASIOExit(); drivers.removeCurrentDriver(); - errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; + errorStream_ << "RtApiAsio::probeDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << info.name << ")."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } info.nativeFormats = 0; @@ -2978,14 +3513,9 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) info.nativeFormats |= RTAUDIO_SINT24; - if ( info.outputChannels > 0 ) - if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; - if ( info.inputChannels > 0 ) - if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; - - info.probed = true; + ASIOExit(); drivers.removeCurrentDriver(); - return info; + return true; } static void bufferSwitch( long index, ASIOBool /*processNow*/ ) @@ -2994,47 +3524,40 @@ static void bufferSwitch( long index, ASIOBool /*processNow*/ ) object->callbackEvent( index ); } -void RtApiAsio :: saveDeviceInfo( void ) -{ - devices_.clear(); - - unsigned int nDevices = getDeviceCount(); - devices_.resize( nDevices ); - for ( unsigned int i=0; isaveDeviceInfo(); - - if ( !drivers.loadDriver( driverName ) ) { + if ( streamOpen ) { + errorText_ = "RtApiAsio::probeDeviceOpen: Another RtAudio ASIO stream is open, cannot open more than one at a time."; + return FAILURE; + } + if ( !drivers.loadDriver( const_cast(driverName.c_str()) ) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; @@ -3042,18 +3565,17 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } - // keep them before any "goto error", they are used for error cleanup + goto device boundary checks bool buffersAllocated = false; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; unsigned int nChannels; - // Check the device channel count. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); @@ -3268,7 +3790,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // prepare for callbacks stream_.sampleRate = sampleRate; - stream_.device[mode] = device; + stream_.deviceId[mode] = deviceId; stream_.mode = isDuplexInput ? DUPLEX : mode; // store this class instance before registering callbacks, that are going to use it @@ -3341,7 +3863,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( RtAudioError::WARNING); // warn but don't fail + error( RTAUDIO_WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; @@ -3353,6 +3875,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + streamOpen = true; return SUCCESS; error: @@ -3363,6 +3886,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( buffersAllocated ) ASIODisposeBuffers(); + ASIOExit(); drivers.removeCurrentDriver(); if ( handle ) { @@ -3387,13 +3911,13 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } return FAILURE; -}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -3401,7 +3925,17 @@ void RtApiAsio :: closeStream() stream_.state = STREAM_STOPPED; ASIOStop(); } + + stream_.state = STREAM_CLOSED; + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + if ( info->deviceDisconnected ) { + // This could be either a disconnect or a sample rate change. + errorText_ = "RtApiAsio: the streaming device was disconnected or the sample rate changed, closing stream!"; + error( RTAUDIO_DEVICE_DISCONNECT ); + } + ASIODisposeBuffers(); + ASIOExit(); drivers.removeCurrentDriver(); AsioHandle *handle = (AsioHandle *) stream_.apiHandle; @@ -3424,25 +3958,28 @@ void RtApiAsio :: closeStream() free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } - - stream_.mode = UNINITIALIZED; - stream_.state = STREAM_CLOSED; + + clearStreamInfo(); + streamOpen = false; + //stream_.mode = UNINITIALIZED; + //stream_.state = STREAM_CLOSED; } -bool stopThreadCalled = false; - -void RtApiAsio :: startStream() +RtAudioErrorType RtApiAsio :: startStream() { - verifyStream(); - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiAsio::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiAsio::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiAsio::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif + */ AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); @@ -3459,19 +3996,18 @@ void RtApiAsio :: startStream() asioXRun = false; unlock: - stopThreadCalled = false; - - if ( result == ASE_OK ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result == ASE_OK ) return RTAUDIO_NO_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } -void RtApiAsio :: stopStream() +RtAudioErrorType RtApiAsio :: stopStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiAsio::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); } AsioHandle *handle = (AsioHandle *) stream_.apiHandle; @@ -3490,17 +4026,18 @@ void RtApiAsio :: stopStream() errorText_ = errorStream_.str(); } - if ( result == ASE_OK ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result == ASE_OK ) return RTAUDIO_NO_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } -void RtApiAsio :: abortStream() +RtAudioErrorType RtApiAsio :: abortStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiAsio::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } // The following lines were commented-out because some behavior was @@ -3510,19 +4047,26 @@ void RtApiAsio :: abortStream() // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // handle->drainCounter = 2; stopStream(); + return RTAUDIO_NO_ERROR; } -// This function will be called by a spawned thread when the user +// This function will be called by a spawned thread when: 1. The user // callback function signals that the stream should be stopped or -// aborted. It is necessary to handle it this way because the -// callbackEvent() function must return before the ASIOStop() -// function will return. +// aborted; or 2. When a signal is received indicating that the device +// sample rate has changed or it has been disconnected. It is +// necessary to handle it this way because the callbackEvent() or +// signaling function must return before the ASIOStop() function will +// return (or the driver can be removed). static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; - object->stopStream(); + if ( info->deviceDisconnected == false ) + object->stopStream(); // drain the stream + else + object->closeStream(); // disconnect or sample rate change ... close the stream + _endthreadex( 0 ); return 0; } @@ -3532,7 +4076,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( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return FAILURE; } @@ -3690,21 +4234,18 @@ static void sampleRateChanged( ASIOSampleRate sRate ) // audio device. RtApi *object = (RtApi *) asioCallbackInfo->object; - try { - object->stopStream(); + if ( object->getStreamSampleRate() != sRate ) { + asioCallbackInfo->deviceDisconnected = true; // flag for either rate change or disconnect + unsigned threadId; + asioCallbackInfo->thread = _beginthreadex( NULL, 0, &asioStopStream, + asioCallbackInfo, 0, &threadId ); } - catch ( RtAudioError &exception ) { - std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; - return; - } - - std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; } static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) { long ret = 0; - + switch( selector ) { case kAsioSelectorSupported: if ( value == kAsioResetRequest @@ -3719,13 +4260,19 @@ static long asioMessages( long selector, long value, void* /*message*/, double* ret = 1L; break; case kAsioResetRequest: - // Defer the task and perform the reset of the driver during the - // next "safe" situation. You cannot reset the driver right now, - // as this code is called from the driver. Reset the driver is - // done by completely destruct is. I.e. ASIOStop(), - // ASIODisposeBuffers(), Destruction Afterwards you initialize the - // driver again. - std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; + // This message is received when a device is disconnected (and + // perhaps when the sample rate changes). It indicates that the + // driver should be reset, which is accomplished by calling + // ASIOStop(), ASIODisposeBuffers() and removing the driver. But + // since this message comes from the driver, we need to let this + // function return before attempting to close the stream and + // remove the driver. Thus, we invoke a thread to initiate the + // stream closing. + asioCallbackInfo->deviceDisconnected = true; // flag for either rate change or disconnect + unsigned threadId; + asioCallbackInfo->thread = _beginthreadex( NULL, 0, &asioStopStream, + asioCallbackInfo, 0, &threadId ); + //std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; ret = 1L; break; case kAsioResyncRequest: @@ -3803,6 +4350,7 @@ static const char* getAsioErrorString( ASIOError result ) #if defined(__WINDOWS_WASAPI__) // Windows WASAPI API // Authored by Marcus Tomlinson , April 2014 +// Updates for new device selection scheme by Gary Scavone, January 2022 // - Introduces support for the Windows WASAPI API // - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required // - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface @@ -3854,6 +4402,7 @@ MIDL_INTERFACE( "00000000-0000-0000-0000-000000000000" ) IAudioClient3 { virtual HRESULT GetSharedModeEnginePeriod( WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32* ) = 0; virtual HRESULT InitializeSharedAudioStream( DWORD, UINT32, WAVEFORMATEX*, LPCGUID ) = 0; + virtual HRESULT Release() = 0; }; #ifdef __CRT_UUID_DECL __CRT_UUID_DECL( IAudioClient3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) @@ -4242,7 +4791,7 @@ struct WasapiHandle renderEvent( NULL ) {} }; -//============================================================================= +//----------------------------------------------------------------------------- RtApiWasapi::RtApiWasapi() : coInitialized_( false ), deviceEnumerator_( NULL ) @@ -4266,219 +4815,359 @@ RtApiWasapi::RtApiWasapi() RtApiWasapi::~RtApiWasapi() { + MUTEX_LOCK( &stream_.mutex ); if ( stream_.state != STREAM_CLOSED ) + { + MUTEX_UNLOCK( &stream_.mutex ); closeStream(); + MUTEX_LOCK( &stream_.mutex ); + } SAFE_RELEASE( deviceEnumerator_ ); // If this object previously called CoInitialize() if ( coInitialized_ ) CoUninitialize(); + MUTEX_UNLOCK( &stream_.mutex ); } -//============================================================================= +//----------------------------------------------------------------------------- -unsigned int RtApiWasapi::getDeviceCount( void ) +unsigned int RtApiWasapi::getDefaultInputDevice( void ) { - unsigned int captureDeviceCount = 0; - unsigned int renderDeviceCount = 0; - - IMMDeviceCollection* captureDevices = NULL; - IMMDeviceCollection* renderDevices = NULL; - - if ( !deviceEnumerator_ ) - return 0; - - // Count capture devices + IMMDevice* devicePtr = NULL; + LPWSTR defaultId = NULL; + std::string id; + + if ( !deviceEnumerator_ ) return 0; // invalid ID errorText_.clear(); - HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + + // Get the default capture device Id. + HRESULT hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &devicePtr ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; - goto Exit; + errorText_ = "RtApiWasapi::getDefaultInputDevice: Unable to retrieve default capture device handle."; + goto Release; } - hr = captureDevices->GetCount( &captureDeviceCount ); + hr = devicePtr->GetId( &defaultId ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; - goto Exit; + errorText_ = "RtApiWasapi::getDefaultInputDevice: Unable to get default capture device Id."; + goto Release; + } + id = convertCharPointerToStdString( defaultId ); + + Release: + SAFE_RELEASE( devicePtr ); + CoTaskMemFree( defaultId ); + + if ( !errorText_.empty() ) { + error( RTAUDIO_DRIVER_ERROR ); + return 0; } - // Count render devices - hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; - goto Exit; + for ( unsigned int m=0; mGetCount( &renderDeviceCount ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; - goto Exit; + // If not found above, then do system probe of devices and try again. + probeDevices(); + for ( unsigned int m=0; mGetDefaultAudioEndpoint( eRender, eConsole, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDefaultOutputDevice: Unable to retrieve default render device handle."; + goto Release; + } + + hr = devicePtr->GetId( &defaultId ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDefaultOutputDevice: Unable to get default render device Id."; + goto Release; + } + id = convertCharPointerToStdString( defaultId ); + + Release: + SAFE_RELEASE( devicePtr ); + CoTaskMemFree( defaultId ); + + if ( !errorText_.empty() ) { + error( RTAUDIO_DRIVER_ERROR ); + return 0; + } + + for ( unsigned int m=0; m > ids; + LPWSTR deviceId = NULL; - // Count capture devices + if ( !deviceEnumerator_ ) return; errorText_.clear(); - RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + + // Count capture devices HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; + errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; + errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; + errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; + errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve render device count."; goto Exit; } - // validate device index - if ( device >= captureDeviceCount + renderDeviceCount ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; - errorType = RtAudioError::INVALID_USE; + nDevices = captureDeviceCount + renderDeviceCount; + if ( nDevices == 0 ) { + errorText_ = "RtApiWasapi::probeDevices: No devices found."; goto Exit; } - // determine whether index falls within capture or render devices - if ( device >= renderDeviceCount ) { - hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + // Get the default capture device Id. + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &devicePtr ); + if ( SUCCEEDED( hr) ) { + hr = devicePtr->GetId( &defaultCaptureId ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; + errorText_ = "RtApiWasapi::probeDevices: Unable to get default capture device Id."; goto Exit; } - isCaptureDevice = true; - } - else { - hr = renderDevices->Item( device, &devicePtr ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; - goto Exit; - } - isCaptureDevice = false; + defaultCaptureString = convertCharPointerToStdString( defaultCaptureId ); } - // get default device name - if ( isCaptureDevice ) { - hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); + // Get the default render device Id. + SAFE_RELEASE( devicePtr ); + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &devicePtr ); + if ( SUCCEEDED( hr) ) { + hr = devicePtr->GetId( &defaultRenderId ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; + errorText_ = "RtApiWasapi::probeDevices: Unable to get default render device Id."; goto Exit; } + defaultRenderString = convertCharPointerToStdString( defaultRenderId ); } - else { - hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); + + // Collect device IDs with mode. + for ( unsigned int n=0; nItem( n, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve render device handle."; + error( RTAUDIO_WARNING ); + continue; + } + } + else { + hr = captureDevices->Item( n - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve capture device handle."; + error( RTAUDIO_WARNING ); + continue; + } + isCaptureDevice = true; + } + + hr = devicePtr->GetId( &deviceId ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; - goto Exit; + errorText_ = "RtApiWasapi::probeDevices: Unable to get device Id."; + error( RTAUDIO_WARNING ); + continue; + } + + ids.push_back( std::pair< std::string, bool>(convertCharPointerToStdString(deviceId), isCaptureDevice) ); + CoTaskMemFree( deviceId ); + } + + // Fill or update the deviceList_ and also save a corresponding list of Ids. + for ( unsigned int n=0; nOpenPropertyStore( STGM_READ, &defaultDevicePropStore ); + // Remove any devices left in the list that are no longer available. + unsigned int m; + for ( std::vector< std::pair< std::string, bool> >::iterator it=deviceIds_.begin(); it!=deviceIds_.end(); ) { + for ( m=0; mGetDevice( deviceId, &devicePtr ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; - goto Exit; - } - PropVariantInit( &defaultDeviceNameProp ); - - hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; + errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device handle."; goto Exit; } - defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal); - - // name + // Get device name hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; + errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to open device property store."; goto Exit; } PropVariantInit( &deviceNameProp ); hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; + if ( FAILED( hr ) || deviceNameProp.pwszVal == nullptr ) { + errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; goto Exit; } - info.name =convertCharPointerToStdString(deviceNameProp.pwszVal); + info.name = convertCharPointerToStdString( deviceNameProp.pwszVal ); - // is default - if ( isCaptureDevice ) { - info.isDefaultInput = info.name == defaultDeviceName; - info.isDefaultOutput = false; - } - else { - info.isDefaultInput = false; - info.isDefaultOutput = info.name == defaultDeviceName; - } - - // channel count + // Get audio client hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; + errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device audio client."; goto Exit; } hr = audioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; + errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device mix format."; goto Exit; } + // Set channel count if ( isCaptureDevice ) { info.inputChannels = deviceFormat->nChannels; info.outputChannels = 0; @@ -4490,16 +5179,16 @@ RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) info.duplexChannels = 0; } - // sample rates + // Set sample rates info.sampleRates.clear(); - // allow support for all sample rates as we have a built-in sample rate converter + // Allow support for all sample rates as we have a built-in sample rate converter. for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { info.sampleRates.push_back( SAMPLE_RATES[i] ); } info.preferredSampleRate = deviceFormat->nSamplesPerSec; - // native format + // Set native formats info.nativeFormats = 0; if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || @@ -4514,8 +5203,8 @@ RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) } } else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || - ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && - ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) { if ( deviceFormat->wBitsPerSample == 8 ) { info.nativeFormats |= RTAUDIO_SINT8; @@ -4531,48 +5220,48 @@ RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) } } - // probed - info.probed = true; - -Exit: - // release all references + 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 ); - if ( !errorText_.empty() ) + if ( !errorText_.empty() ) { error( errorType ); - return info; + return false; + } + return true; } void RtApiWasapi::closeStream( void ) { + MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiWasapi::closeStream: No open stream to close."; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); + MUTEX_UNLOCK( &stream_.mutex ); return; } if ( stream_.state != STREAM_STOPPED ) + { + MUTEX_UNLOCK( &stream_.mutex ); stopStream(); + MUTEX_LOCK( &stream_.mutex ); + } // clean up stream memory + SAFE_RELEASE(((WasapiHandle*)stream_.apiHandle)->captureClient) + SAFE_RELEASE(((WasapiHandle*)stream_.apiHandle)->renderClient) + 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 ); @@ -4594,26 +5283,30 @@ void RtApiWasapi::closeStream( void ) stream_.deviceBuffer = 0; } - // update stream state - stream_.state = STREAM_CLOSED; + clearStreamInfo(); + MUTEX_UNLOCK( &stream_.mutex ); } //----------------------------------------------------------------------------- -void RtApiWasapi::startStream( void ) +RtAudioErrorType RtApiWasapi::startStream( void ) { - verifyStream(); - - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiWasapi::startStream: The stream is already running."; - error( RtAudioError::WARNING ); - return; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiWasapi::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiWasapi::startStream(): the stream is stopping or closed!"; + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_WARNING ); } + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif - + */ + // update stream state stream_.state = STREAM_RUNNING; @@ -4622,29 +5315,29 @@ void RtApiWasapi::startStream( void ) if ( !stream_.callbackInfo.thread ) { errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; - error( RtAudioError::THREAD_ERROR ); + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_THREAD_ERROR ); } else { SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); ResumeThread( ( void* ) stream_.callbackInfo.thread ); } + MUTEX_UNLOCK( &stream_.mutex ); + return RTAUDIO_NO_ERROR; } //----------------------------------------------------------------------------- -void RtApiWasapi::stopStream( void ) +RtAudioErrorType RtApiWasapi::stopStream( void ) { - verifyStream(); - - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; - error( RtAudioError::WARNING ); - return; - } - if ( stream_.state == STREAM_STOPPING ) { - errorText_ = "RtApiWasapi::stopStream: The stream is already stopping."; - error( RtAudioError::WARNING ); - return; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiWasapi::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiWasapi::stopStream(): the stream is closed!"; + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_WARNING ); } // inform stream thread by setting stream state to STREAM_STOPPING @@ -4655,30 +5348,29 @@ void RtApiWasapi::stopStream( void ) // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; - error( RtAudioError::THREAD_ERROR ); - return; + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_THREAD_ERROR ); } stream_.callbackInfo.thread = (ThreadHandle) NULL; + MUTEX_UNLOCK( &stream_.mutex ); + return RTAUDIO_NO_ERROR; } //----------------------------------------------------------------------------- -void RtApiWasapi::abortStream( void ) +RtAudioErrorType RtApiWasapi::abortStream( void ) { - verifyStream(); - - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; - error( RtAudioError::WARNING ); - return; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiWasapi::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiWasapi::abortStream(): the stream is stopping or closed!"; + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_WARNING ); } - if ( stream_.state == STREAM_STOPPING ) { - errorText_ = "RtApiWasapi::abortStream: The stream is already stopping."; - error( RtAudioError::WARNING ); - return; - } - + // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; @@ -4687,87 +5379,71 @@ void RtApiWasapi::abortStream( void ) // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; - error( RtAudioError::THREAD_ERROR ); - return; + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_THREAD_ERROR ); } stream_.callbackInfo.thread = (ThreadHandle) NULL; + MUTEX_UNLOCK( &stream_.mutex ); + return RTAUDIO_NO_ERROR; } //----------------------------------------------------------------------------- -bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, +bool RtApiWasapi::probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) { + MUTEX_LOCK( &stream_.mutex ); bool methodResult = FAILURE; - unsigned int captureDeviceCount = 0; - unsigned int renderDeviceCount = 0; - - IMMDeviceCollection* captureDevices = NULL; - IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; + bool isInput = false; + std::string id; - // create API Handle if not already created + unsigned int deviceIdx; + for ( deviceIdx=0; deviceIdxGetDevice( (LPWSTR)temp.c_str(), &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device handle."; + MUTEX_UNLOCK( &stream_.mutex ); + return FAILURE; + } + + // Create API handle if not already created. if ( !stream_.apiHandle ) stream_.apiHandle = ( void* ) new WasapiHandle(); - // Count capture devices - errorText_.clear(); - RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; - HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; - goto Exit; - } - - hr = captureDevices->GetCount( &captureDeviceCount ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; - goto Exit; - } - - // Count render devices - hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; - goto Exit; - } - - hr = renderDevices->GetCount( &renderDeviceCount ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; - goto Exit; - } - - // validate device index - if ( device >= captureDeviceCount + renderDeviceCount ) { - errorType = RtAudioError::INVALID_USE; - errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; - goto Exit; - } - - // if device index falls within capture devices - if ( device >= renderDeviceCount ) { - if ( mode != INPUT ) { - errorType = RtAudioError::INVALID_USE; - errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; - goto Exit; - } - - // retrieve captureAudioClient from devicePtr + if ( isInput ) { IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; - hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; - goto Exit; - } - hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { @@ -4785,25 +5461,19 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } - // if device index falls within render devices and is configured for loopback - if ( device < renderDeviceCount && mode == INPUT ) - { - // if renderAudioClient is not initialised, initialise it now + // If an output device and is configured for loopback (input mode) + if ( isInput == false && mode == INPUT ) { + // If renderAudioClient is not initialised, initialise it now IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; - if ( !renderAudioClient ) - { - probeDeviceOpen( device, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); + if ( !renderAudioClient ) { + MUTEX_UNLOCK( &stream_.mutex ); + probeDeviceOpen( deviceId, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); + MUTEX_LOCK( &stream_.mutex ); } - // retrieve captureAudioClient from devicePtr + // Retrieve captureAudioClient from our stream handle. IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; - hr = renderDevices->Item( device, &devicePtr ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; - goto Exit; - } - hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { @@ -4821,23 +5491,15 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } - // if device index falls within render devices and is configured for output - if ( device < renderDeviceCount && mode == OUTPUT ) - { - // if renderAudioClient is already initialised, don't initialise it again + // If output device and is configured for output. + if ( isInput == false && mode == OUTPUT ) { + // If renderAudioClient is already initialised, don't initialise it again IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; - if ( renderAudioClient ) - { + if ( renderAudioClient ) { methodResult = SUCCESS; goto Exit; } - hr = renderDevices->Item( device, &devicePtr ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; - goto Exit; - } - hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { @@ -4855,7 +5517,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } - // fill stream data + // Fill stream data if ( ( stream_.mode == OUTPUT && mode == INPUT ) || ( stream_.mode == INPUT && mode == OUTPUT ) ) { stream_.mode = DUPLEX; @@ -4864,7 +5526,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.mode = mode; } - stream_.device[mode] = device; + stream_.deviceId[mode] = deviceId; stream_.doByteSwap[mode] = false; stream_.sampleRate = sampleRate; stream_.bufferSize = *bufferSize; @@ -4872,7 +5534,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; - stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; + stream_.deviceFormat[mode] = deviceList_[deviceIdx].nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; @@ -4898,7 +5560,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); if ( !stream_.userBuffer[mode] ) { - errorType = RtAudioError::MEMORY_ERROR; + errorType = RTAUDIO_MEMORY_ERROR; errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; goto Exit; } @@ -4913,19 +5575,23 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne methodResult = SUCCESS; -Exit: + Exit: //clean up - SAFE_RELEASE( captureDevices ); - SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); CoTaskMemFree( deviceFormat ); // if method failed, close the stream if ( methodResult == FAILURE ) + { + MUTEX_UNLOCK( &stream_.mutex ); closeStream(); + MUTEX_LOCK( &stream_.mutex ); + } if ( !errorText_.empty() ) error( errorType ); + + MUTEX_UNLOCK( &stream_.mutex ); return methodResult; } @@ -4987,7 +5653,7 @@ void RtApiWasapi::wasapiThread() unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; - bool loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT]; + bool loopbackEnabled = stream_.deviceId[INPUT] == stream_.deviceId[OUTPUT]; bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; @@ -4999,7 +5665,7 @@ void RtApiWasapi::wasapiThread() unsigned int deviceBuffSize = 0; std::string errorText; - RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + RtAudioErrorType errorType = RTAUDIO_DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibraryW( L"AVRT.dll" ); @@ -5047,6 +5713,7 @@ void RtApiWasapi::wasapiThread() MinPeriodInFrames, captureFormat, NULL ); + SAFE_RELEASE(captureAudioClient3); } else { @@ -5076,7 +5743,7 @@ void RtApiWasapi::wasapiThread() // configure captureEvent to trigger on every available capture buffer captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !captureEvent ) { - errorType = RtAudioError::SYSTEM_ERROR; + errorType = RTAUDIO_SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; goto Exit; } @@ -5158,6 +5825,7 @@ void RtApiWasapi::wasapiThread() MinPeriodInFrames, renderFormat, NULL ); + SAFE_RELEASE(renderAudioClient3); } else { @@ -5184,7 +5852,7 @@ void RtApiWasapi::wasapiThread() // configure renderEvent to trigger on every available render buffer renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { - errorType = RtAudioError::SYSTEM_ERROR; + errorType = RTAUDIO_SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } @@ -5252,7 +5920,7 @@ void RtApiWasapi::wasapiThread() convBuffer = ( char* ) calloc( convBuffSize, 1 ); stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { - errorType = RtAudioError::MEMORY_ERROR; + errorType = RTAUDIO_MEMORY_ERROR; errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } @@ -5341,12 +6009,12 @@ void RtApiWasapi::wasapiThread() // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { - errorType = RtAudioError::THREAD_ERROR; + errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { - errorType = RtAudioError::THREAD_ERROR; + errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } @@ -5357,12 +6025,12 @@ void RtApiWasapi::wasapiThread() // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { - errorType = RtAudioError::THREAD_ERROR; + errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { - errorType = RtAudioError::THREAD_ERROR; + errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } @@ -5641,13 +6309,13 @@ static const char* getErrorString( int code ); static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { - LPGUID id[2]; - bool validId[2]; - bool found; + LPGUID id; + bool isInput; std::string name; + std::string epID; // endpoint ID DsDevice() - : found(false) { validId[0] = false; validId[1] = false; } + : isInput(false) {} }; struct DsProbeData { @@ -5670,86 +6338,91 @@ RtApiDs :: ~RtApiDs() if ( coInitialized_ ) CoUninitialize(); // balanced call. } -// The DirectSound default output is always the first device. -unsigned int RtApiDs :: getDefaultOutputDevice( void ) +void RtApiDs :: probeDevices( void ) { - return 0; -} - -// The DirectSound default input is always the first input device, -// which is the first capture device enumerated. -unsigned int RtApiDs :: getDefaultInputDevice( void ) -{ - return 0; -} - -unsigned int RtApiDs :: getDeviceCount( void ) -{ - // Set query flag for previously found devices to false, so that we - // can check for any devices that have disappeared. - for ( unsigned int i=0; i devices; + probeInfo.dsDevices = &devices; HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { - errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating output devices!"; + errorStream_ << "RtApiDs::probeDevices: error (" << getErrorString( result ) << ") enumerating output devices!"; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); } // Query DirectSoundCapture devices. probeInfo.isInput = true; result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { - errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating input devices!"; + errorStream_ << "RtApiDs::probeDevices: error (" << getErrorString( result ) << ") enumerating input devices!"; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); } - // Clean out any devices that may have disappeared (code update submitted by Eli Zehngut). - for ( unsigned int i=0; i(dsDevices.size()); -} - -RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) -{ - RtAudio::DeviceInfo info; - info.probed = false; - - if ( dsDevices.size() == 0 ) { - // Force a query of all devices - getDeviceCount(); - if ( dsDevices.size() == 0 ) { - errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; - error( RtAudioError::INVALID_USE ); - return info; + // Now fill or update our deviceList_ vector. + unsigned int m, n; + for ( n=0; n= dsDevices.size() ) { - errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; - error( RtAudioError::INVALID_USE ); - return info; + // Remove any devices left in the list that are no longer available. + for ( std::vector< struct DsDevice >::iterator it=dsDevices_.begin(); it!=dsDevices_.end(); ) { + for ( n=0; nGetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); - errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; + errorStream_ << "RtApiDs::probeDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto probeInput; } @@ -5784,24 +6457,18 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) output->Release(); - if ( getDefaultOutputDevice() == device ) - info.isDefaultOutput = true; - - if ( dsDevices[ device ].validId[1] == false ) { - info.name = dsDevices[ device ].name; - info.probed = true; - return info; - } + info.name = dsDevice.name; + return true; probeInput: LPDIRECTSOUNDCAPTURE input; - result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + result = DirectSoundCaptureCreate( dsDevice.id, &input, NULL ); if ( FAILED( result ) ) { - errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevice.name << ")!"; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } DSCCAPS inCaps; @@ -5809,10 +6476,10 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); - errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevice.name << ")!"; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } // Get input channel information. @@ -5870,7 +6537,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) input->Release(); - if ( info.inputChannels == 0 ) return info; + if ( info.inputChannels == 0 ) return false; // Copy the supported rates to the info structure but avoid duplication. bool found; @@ -5885,20 +6552,17 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) if ( found == false ) info.sampleRates.push_back( rates[i] ); } std::sort( info.sampleRates.begin(), info.sampleRates.end() ); - - // If device opens for both playback and capture, we determine the channels. - if ( info.outputChannels > 0 && info.inputChannels > 0 ) - info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; - - if ( device == 0 ) info.isDefaultInput = true; + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = info.sampleRates[i]; + } // Copy name and return. - info.name = dsDevices[ device ].name; - info.probed = true; - return info; + info.name = dsDevice.name; + return true; } -bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, +bool RtApiDs :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) @@ -5908,29 +6572,36 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned return FAILURE; } - size_t 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!"; return FAILURE; } - if ( device >= nDevices ) { - // This should not happen because a check is made before this function is called. + int deviceIdx = -1; + for ( unsigned int m=0; mGetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { - errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; + errorStream_ << "RtApiDs::probeDeviceInfo: the output device (" << dsDevices_[ deviceIdx ].name << ") does not support stereo playback."; errorText_ = errorStream_.str(); return FAILURE; } @@ -6029,7 +6700,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); if ( FAILED( result ) ) { output->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6048,7 +6719,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6057,7 +6728,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = buffer->SetFormat( &waveFormat ); if ( FAILED( result ) ) { output->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6083,7 +6754,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6096,7 +6767,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( FAILED( result ) ) { output->Release(); buffer->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6110,7 +6781,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( FAILED( result ) ) { output->Release(); buffer->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6123,7 +6794,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( FAILED( result ) ) { output->Release(); buffer->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6135,9 +6806,9 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( mode == INPUT ) { LPDIRECTSOUNDCAPTURE input; - result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + result = DirectSoundCaptureCreate( dsDevices_[ deviceIdx ].id, &input, NULL ); if ( FAILED( result ) ) { - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6147,14 +6818,14 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( inCaps.dwChannels < channels + firstChannel ) { - errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; + errorText_ = "RtApiDs::probeDeviceInfo: the input device does not support requested input channels."; return FAILURE; } @@ -6208,7 +6879,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { input->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6220,7 +6891,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( FAILED( result ) ) { input->Release(); buffer->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6239,7 +6910,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( FAILED( result ) ) { input->Release(); buffer->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6252,7 +6923,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( FAILED( result ) ) { input->Release(); buffer->Release(); - errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } @@ -6335,7 +7006,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned handle->dsBufferSize[mode] = dsBufferSize; handle->dsPointerLeadTime[mode] = dsPointerLeadTime; - stream_.device[mode] = device; + stream_.deviceId[mode] = deviceIdx; stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up an output stream. @@ -6404,7 +7075,7 @@ void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -6450,23 +7121,27 @@ void RtApiDs :: closeStream() stream_.deviceBuffer = 0; } - stream_.mode = UNINITIALIZED; - stream_.state = STREAM_CLOSED; + clearStreamInfo(); + //stream_.mode = UNINITIALIZED; + //stream_.state = STREAM_CLOSED; } -void RtApiDs :: startStream() +RtAudioErrorType RtApiDs :: startStream() { - verifyStream(); - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiDs::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiDs::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiDs::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif - + */ + DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of @@ -6511,16 +7186,18 @@ void RtApiDs :: startStream() stream_.state = STREAM_RUNNING; unlock: - if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); + if ( FAILED( result ) ) error( RTAUDIO_SYSTEM_ERROR ); + return RTAUDIO_NO_ERROR; } -void RtApiDs :: stopStream() +RtAudioErrorType RtApiDs :: stopStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiDs::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); } HRESULT result = 0; @@ -6615,22 +7292,24 @@ void RtApiDs :: stopStream() timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. MUTEX_UNLOCK( &stream_.mutex ); - if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); + if ( FAILED( result ) ) error( RTAUDIO_SYSTEM_ERROR ); + return RTAUDIO_NO_ERROR; } -void RtApiDs :: abortStream() +RtAudioErrorType RtApiDs :: abortStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiDs::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } DsHandle *handle = (DsHandle *) stream_.apiHandle; handle->drainCounter = 2; - stopStream(); + return stopStream(); } void RtApiDs :: callbackEvent() @@ -6642,7 +7321,7 @@ void RtApiDs :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -6735,7 +7414,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); @@ -6743,7 +7422,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } while ( true ) { @@ -6752,7 +7431,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); @@ -6760,7 +7439,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; @@ -6782,7 +7461,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; @@ -6834,7 +7513,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } @@ -6876,7 +7555,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } @@ -6890,7 +7569,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; @@ -6927,7 +7606,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } @@ -6989,7 +7668,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } @@ -7004,7 +7683,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } @@ -7026,7 +7705,7 @@ void RtApiDs :: callbackEvent() errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); + error( RTAUDIO_SYSTEM_ERROR ); return; } handle->bufferPointer[1] = nextReadPointer; @@ -7066,7 +7745,7 @@ static unsigned __stdcall callbackHandler( void *ptr ) static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, - LPCTSTR /*module*/, + LPCTSTR lpctstr, LPVOID lpContext ) { struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; @@ -7104,37 +7783,13 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, object->Release(); } - // If good device, then save its name and guid. - std::string name = convertCharPointerToStdString( description ); - if ( validDevice ) { - for ( unsigned int i=0; i> deviceID_prettyName; + snd_pcm_stream_t stream; + std::string defaultDeviceName; - strcpy(name, "default"); + // Add the default interface if available. result = snd_ctl_open( &handle, "default", 0 ); if (result == 0) { - nDevices++; + deviceID_prettyName.push_back({"default", "Default ALSA Device"}); + defaultDeviceName = deviceID_prettyName[0].second; snd_ctl_close( handle ); } - // Count cards and devices + // Add the Pulse interface if available. + result = snd_ctl_open( &handle, "pulse", 0 ); + if (result == 0) { + deviceID_prettyName.push_back({"pulse", "PulseAudio Sound Server"}); + snd_ctl_close( handle ); + } + + // Count cards and devices and get ascii identifiers. card = -1; snd_card_next( &card ); while ( card >= 0 ) { @@ -7254,141 +7926,151 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { handle = 0; - errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDevices: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto nextcard; } - subdevice = -1; + result = snd_ctl_card_info( handle, ctlinfo ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::probeDevices: control info, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RTAUDIO_WARNING ); + goto nextcard; + } + device = -1; while( 1 ) { - result = snd_ctl_pcm_next_device( handle, &subdevice ); + result = snd_ctl_pcm_next_device( handle, &device ); if ( result < 0 ) { - errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDevices: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); break; } - if ( subdevice < 0 ) + if ( device < 0 ) break; - nDevices++; + + snd_pcm_info_set_device( pcminfo, device ); + snd_pcm_info_set_subdevice( pcminfo, 0 ); + stream = SND_PCM_STREAM_PLAYBACK; + snd_pcm_info_set_stream( pcminfo, stream ); + result = snd_ctl_pcm_info( handle, pcminfo ); + if ( result < 0 ) { + if ( result == -ENOENT ) { // try as input stream + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + result = snd_ctl_pcm_info( handle, pcminfo ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::probeDevices: control pcm info, card = " << card << ", device = " << device << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RTAUDIO_WARNING ); + continue; + } + } + else continue; + } + sprintf( name, "hw:%s,%d", snd_ctl_card_info_get_id(ctlinfo), device ); + std::string id(name); + sprintf( name, "%s (%s)", snd_ctl_card_info_get_name(ctlinfo), snd_pcm_info_get_id(pcminfo) ); + std::string prettyName(name); + deviceID_prettyName.push_back( {id, prettyName} ); + if ( card == 0 && device == 0 && defaultDeviceName.empty() ) + defaultDeviceName = name; } nextcard: if ( handle ) - snd_ctl_close( handle ); + snd_ctl_close( handle ); snd_card_next( &card ); } - return nDevices; -} - -RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) -{ - RtAudio::DeviceInfo info; - info.probed = false; - - unsigned nDevices = 0; - int result=-1, subdevice=-1, card=-1; - char name[64]; - snd_ctl_t *chandle = 0; - - result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); - if ( result == 0 ) { - if ( nDevices++ == device ) { - strcpy( name, "default" ); - goto foundDevice; - } + if ( deviceID_prettyName.size() == 0 ) { + deviceList_.clear(); + deviceIdPairs_.clear(); + return; } - if ( chandle ) - snd_ctl_close( chandle ); - // Count cards and devices - snd_card_next( &card ); - while ( card >= 0 ) { - sprintf( name, "hw:%d", card ); - result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); - if ( result < 0 ) { - chandle = 0; - errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; - errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - goto nextcard; - } - subdevice = -1; - while( 1 ) { - result = snd_ctl_pcm_next_device( chandle, &subdevice ); - if ( result < 0 ) { - errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; - errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + // Clean removed devices + for ( auto it = deviceIdPairs_.begin(); it != deviceIdPairs_.end(); ) { + bool found = false; + for ( auto& d: deviceID_prettyName ) { + if ( d.first == (*it).first ) { + found = true; break; } - if ( subdevice < 0 ) break; - if ( nDevices == device ) { - sprintf( name, "hw:%d,%d", card, subdevice ); - goto foundDevice; + } + + if ( found ) + ++it; + else + it = deviceIdPairs_.erase(it); + } + + // Fill or update the deviceList_ and also save a corresponding list of Ids. + for ( auto& d : deviceID_prettyName ) { + bool found = false; + for ( auto& dID : deviceIdPairs_ ) { + if ( d.first == dID.first ) { + found = true; + break; // We already have this device. } - nDevices++; } - nextcard: - if ( chandle ) - snd_ctl_close( chandle ); - snd_card_next( &card ); - } - if ( nDevices == 0 ) { - errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; - error( RtAudioError::INVALID_USE ); - return info; - } + if ( found ) + continue; - if ( device >= nDevices ) { - errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; - error( RtAudioError::INVALID_USE ); - return info; - } - - foundDevice: - - // If a stream is already open, we cannot probe the stream devices. - // Thus, use the saved results. - if ( stream_.state != STREAM_CLOSED && - ( stream_.device[0] == device || stream_.device[1] == device ) ) { - snd_ctl_close( chandle ); - if ( device >= devices_.size() ) { - errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; - error( RtAudioError::WARNING ); - return info; + // new device + RtAudio::DeviceInfo info; + info.name = d.second; + if ( probeDeviceInfo( info, d.first ) == false ) continue; // ignore if probe fails + info.ID = currentDeviceId_++; // arbitrary internal device ID + if ( info.name == defaultDeviceName ) { + if ( info.outputChannels > 0 ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) info.isDefaultInput = true; } - return devices_[ device ]; + deviceList_.push_back( info ); + deviceIdPairs_.push_back({d.first, info.ID}); + // I don't see that ALSA provides property listeners to know if + // devices are removed or added. } - int openMode = SND_PCM_ASYNC; + // Remove any devices left in the list that are no longer available. + for ( std::vector::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) + { + auto itID = deviceIdPairs_.begin(); + while ( itID != deviceIdPairs_.end() ) { + if ( (*it).ID == (*itID).second ) { + break; + } + ++itID; + } + + if ( itID == deviceIdPairs_.end() ) { + // not found so remove it from our list + it = deviceList_.erase( it ); + } + else + ++it; + } +} + +bool RtApiAlsa :: probeDeviceInfo( RtAudio::DeviceInfo& info, std::string name ) +{ + int result, openMode = SND_PCM_ASYNC; snd_pcm_stream_t stream; - snd_pcm_info_t *pcminfo; - snd_pcm_info_alloca( &pcminfo ); snd_pcm_t *phandle; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); - // First try for playback unless default device (which has subdev -1) + // First try for playback stream = SND_PCM_STREAM_PLAYBACK; - 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_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); + result = snd_pcm_open( &phandle, name.c_str(), 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( RtAudioError::WARNING ); + if ( result == -16 ) return false; // device busy ... can't probe or use + if ( result != -2 ) { // device doesn't support playback + errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_open (playback) error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RTAUDIO_WARNING ); + } goto captureProbe; } @@ -7396,9 +8078,9 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto captureProbe; } @@ -7407,9 +8089,9 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto captureProbe; } info.outputChannels = value; @@ -7417,27 +8099,14 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) captureProbe: stream = SND_PCM_STREAM_CAPTURE; - snd_pcm_info_set_stream( pcminfo, stream ); - - // 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; + result = snd_pcm_open( &phandle, name.c_str(), stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 && result ) { + if ( result != -2 && result != -16 ) { // device busy or doesn't support capture + errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_open (capture) error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RTAUDIO_WARNING ); } - } - 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( RtAudioError::WARNING ); - if ( info.outputChannels == 0 ) return info; + if ( info.outputChannels == 0 ) return false; goto probeParameters; } @@ -7445,20 +8114,20 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - if ( info.outputChannels == 0 ) return info; + error( RTAUDIO_WARNING ); + if ( info.outputChannels == 0 ) return false; goto probeParameters; } result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - if ( info.outputChannels == 0 ) return info; + error( RTAUDIO_WARNING ); + if ( info.outputChannels == 0 ) return false; goto probeParameters; } info.inputChannels = value; @@ -7468,12 +8137,6 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; - // ALSA doesn't provide default devices so we'll use the first available one. - if ( device == 0 && info.outputChannels > 0 ) - info.isDefaultOutput = true; - if ( device == 0 && info.inputChannels > 0 ) - info.isDefaultInput = true; - probeParameters: // At this point, we just need to figure out the supported data // formats and sample rates. We'll proceed by opening the device in @@ -7485,24 +8148,23 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; - snd_pcm_info_set_stream( pcminfo, stream ); - result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + result = snd_pcm_open( &phandle, name.c_str(), stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { - errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } // Test our discrete set of sample rate values. @@ -7517,10 +8179,10 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) } if ( info.sampleRates.size() == 0 ) { snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } // Probe the supported data formats ... we don't care about endian-ness just yet @@ -7548,40 +8210,18 @@ 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."; + errorStream_ << "RtApiAlsa::probeDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); - return info; + error( RTAUDIO_WARNING ); + return false; } - // Get the device name - if (strncmp(name, "default", 7)!=0) { - char *cardname; - result = snd_card_get_name( card, &cardname ); - if ( result >= 0 ) { - sprintf( name, "hw:%s,%d", cardname, subdevice ); - free( cardname ); - } - } - info.name = name; - - // That's all ... close the device and return + // Close the device and return snd_pcm_close( phandle ); - info.probed = true; - return info; + return true; } -void RtApiAlsa :: saveDeviceInfo( void ) -{ - devices_.clear(); - - unsigned int nDevices = getDeviceCount(); - devices_.resize( nDevices ); - for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT) ) - { - strcpy(name, "default"); - result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); - if ( result == 0 ) { - if ( nDevices == device ) { - strcpy( name, "default" ); - snd_ctl_close( chandle ); - goto foundDevice; - } - nDevices++; + std::string name; + for ( auto& id : deviceIdPairs_) { + if ( id.second == deviceId ) { + name = id.first; + break; } } - else { - nDevices++; - // Count cards and devices - card = -1; - snd_card_next( &card ); - while ( card >= 0 ) { - sprintf( name, "hw:%d", card ); - result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); - if ( result < 0 ) { - errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; - errorText_ = errorStream_.str(); - return FAILURE; - } - subdevice = -1; - while( 1 ) { - result = snd_ctl_pcm_next_device( chandle, &subdevice ); - if ( result < 0 ) break; - if ( subdevice < 0 ) break; - if ( nDevices == device ) { - sprintf( name, "hw:%d,%d", card, subdevice ); - snd_ctl_close( chandle ); - goto foundDevice; - } - nDevices++; - } - snd_ctl_close( chandle ); - snd_card_next( &card ); - } - - if ( nDevices == 0 ) { - // This should not happen because a check is made before this function is called. - errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; - return FAILURE; - } - - if ( device >= nDevices ) { - // This should not happen because a check is made before this function is called. - errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; - return FAILURE; - } - } - - foundDevice: - - // The getDeviceInfo() function will not work for a device that is - // already open. Thus, we'll probe the system before opening a - // stream and save the results for use by getDeviceInfo(). - if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once - this->saveDeviceInfo(); - snd_pcm_stream_t stream; if ( mode == OUTPUT ) stream = SND_PCM_STREAM_PLAYBACK; @@ -7676,7 +8252,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne snd_pcm_t *phandle; int openMode = SND_PCM_ASYNC; - result = snd_pcm_open( &phandle, name, stream, openMode ); + int result = snd_pcm_open( &phandle, name.c_str(), stream, openMode ); if ( result < 0 ) { if ( mode == OUTPUT ) errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; @@ -7792,7 +8368,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // If we get here, no supported format was found. snd_pcm_close( phandle ); - errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; @@ -8011,7 +8587,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.sampleRate = sampleRate; stream_.nBuffers = periods; - stream_.device[mode] = device; + stream_.deviceId[mode] = deviceId; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. @@ -8027,7 +8603,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( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); } } else { @@ -8116,7 +8692,7 @@ void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -8158,27 +8734,29 @@ void RtApiAlsa :: closeStream() stream_.deviceBuffer = 0; } - stream_.mode = UNINITIALIZED; - stream_.state = STREAM_CLOSED; + clearStreamInfo(); } -void RtApiAlsa :: startStream() +RtAudioErrorType RtApiAlsa :: startStream() { // This method calls snd_pcm_prepare if the device isn't already in that state. - verifyStream(); - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiAlsa::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif - + */ + int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; @@ -8215,17 +8793,18 @@ void RtApiAlsa :: startStream() pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); - if ( result >= 0 ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result < 0 ) return error( RTAUDIO_SYSTEM_ERROR ); + return RTAUDIO_NO_ERROR; } -void RtApiAlsa :: stopStream() +RtAudioErrorType RtApiAlsa :: stopStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiAlsa::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); } stream_.state = STREAM_STOPPED; @@ -8259,17 +8838,18 @@ void RtApiAlsa :: stopStream() apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); - if ( result >= 0 ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result < 0 ) return error( RTAUDIO_SYSTEM_ERROR ); + return RTAUDIO_NO_ERROR; } -void RtApiAlsa :: abortStream() +RtAudioErrorType RtApiAlsa :: abortStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiAlsa::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } stream_.state = STREAM_STOPPED; @@ -8300,8 +8880,8 @@ void RtApiAlsa :: abortStream() apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); - if ( result >= 0 ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result < 0 ) return error( RTAUDIO_SYSTEM_ERROR ); + return RTAUDIO_NO_ERROR; } void RtApiAlsa :: callbackEvent() @@ -8321,7 +8901,7 @@ void RtApiAlsa :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -8404,7 +8984,7 @@ void RtApiAlsa :: callbackEvent() errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto tryOutput; } @@ -8476,7 +9056,7 @@ void RtApiAlsa :: callbackEvent() errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto unlock; } @@ -8519,29 +9099,24 @@ static void *alsaCallbackHandler( void *ptr ) #if defined(__LINUX_PULSE__) -// Code written by Peter Meerwald, pmeerw@pmeerw.net -// and Tristan Matthews. +// Code written by Peter Meerwald, pmeerw@pmeerw.net and Tristan Matthews. +// Updated by Gary Scavone, 2021. #include #include -#include #include -static pa_mainloop_api *rt_pa_mainloop_api = NULL; -struct PaDeviceInfo { - PaDeviceInfo() : sink_index(-1), source_index(-1) {} - int sink_index; - int source_index; - std::string sink_name; - std::string source_name; - RtAudio::DeviceInfo info; +// A structure needed to pass variables for device probing. +struct PaDeviceProbeInfo { + pa_mainloop_api *paMainLoopApi; + std::string defaultSinkName; + std::string defaultSourceName; + int defaultRate; + unsigned int *currentDeviceId; + std::vector< std::string > deviceNames; + std::vector< RtApiPulse::PaDeviceInfo > *paDeviceList; + std::vector< RtAudio::DeviceInfo > *rtDeviceList; }; -static struct { - std::vector dev; - std::string default_sink_name; - std::string default_source_name; - int default_rate; -} rt_pa_info; static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 96000, 192000, 0}; @@ -8567,118 +9142,106 @@ struct PulseAudioHandle { PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; -static void rt_pa_mainloop_api_quit(int ret) { - rt_pa_mainloop_api->quit(rt_pa_mainloop_api, ret); -} - -static void rt_pa_set_server_info(pa_context *context, const pa_server_info *info, void *data){ +// The following 3 functions are called by the device probing +// system. This first one gets overall system information. +static void rt_pa_set_server_info( pa_context *context, const pa_server_info *info, void *userdata ) +{ (void)context; - (void)data; pa_sample_spec ss; + PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); if (!info) { - rt_pa_mainloop_api_quit(1); + paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 1 ); return; } ss = info->sample_spec; - - rt_pa_info.default_rate = ss.rate; - rt_pa_info.default_sink_name = info->default_sink_name; - rt_pa_info.default_source_name = info->default_source_name; + paProbeInfo->defaultRate = ss.rate; + paProbeInfo->defaultSinkName = info->default_sink_name; + paProbeInfo->defaultSourceName = info->default_source_name; } -static void rt_pa_set_sink_info(pa_context * /*c*/, const pa_sink_info *i, - int eol, void * /*userdata*/) +// Used to get output device information. +static void rt_pa_set_sink_info( pa_context * /*c*/, const pa_sink_info *i, + int eol, void *userdata ) { - if (eol) return; - PaDeviceInfo inf; - inf.info.name = pa_proplist_gets(i->proplist, "device.description"); - inf.info.probed = true; - inf.info.outputChannels = i->sample_spec.channels; - inf.info.preferredSampleRate = i->sample_spec.rate; - inf.info.isDefaultOutput = (rt_pa_info.default_sink_name == i->name); - inf.sink_index = i->index; - inf.sink_name = i->name; + if ( eol ) return; + + PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); + std::string name = pa_proplist_gets( i->proplist, "device.description" ); + paProbeInfo->deviceNames.push_back( name ); + for ( size_t n=0; nrtDeviceList->size(); n++ ) + if ( paProbeInfo->rtDeviceList->at(n).name == name ) return; // we've already probed this one + + RtAudio::DeviceInfo info; + info.name = name; + info.outputChannels = i->sample_spec.channels; + info.preferredSampleRate = i->sample_spec.rate; + info.isDefaultOutput = ( paProbeInfo->defaultSinkName == i->name ); for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) - inf.info.sampleRates.push_back( *sr ); - for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; - fm->rtaudio_format; ++fm ) - inf.info.nativeFormats |= fm->rtaudio_format; - for (size_t i=0; i < rt_pa_info.dev.size(); i++) - { - /* Attempt to match up sink and source records by device description. */ - if (rt_pa_info.dev[i].info.name == inf.info.name) { - rt_pa_info.dev[i].sink_index = inf.sink_index; - rt_pa_info.dev[i].sink_name = inf.sink_name; - rt_pa_info.dev[i].info.outputChannels = inf.info.outputChannels; - rt_pa_info.dev[i].info.isDefaultOutput = inf.info.isDefaultOutput; - /* Assume duplex channels are minimum of input and output channels. */ - /* Uncomment if we add support for DUPLEX - if (rt_pa_info.dev[i].source_index > -1) - (inf.info.outputChannels < rt_pa_info.dev[i].info.inputChannels) - ? inf.info.outputChannels : rt_pa_info.dev[i].info.inputChannels; - */ - return; - } - } - /* try to ensure device #0 is the default */ - if (inf.info.isDefaultOutput) - rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); - else - rt_pa_info.dev.push_back(inf); + info.sampleRates.push_back( *sr ); + for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; fm->rtaudio_format; ++fm ) + info.nativeFormats |= fm->rtaudio_format; + info.ID = *(paProbeInfo->currentDeviceId); + *(paProbeInfo->currentDeviceId) = info.ID + 1; + paProbeInfo->rtDeviceList->push_back( info ); + + RtApiPulse::PaDeviceInfo painfo; + painfo.sinkName = i->name; + paProbeInfo->paDeviceList->push_back( painfo ); } -static void rt_pa_set_source_info_and_quit(pa_context * /*c*/, const pa_source_info *i, - int eol, void * /*userdata*/) +// Used to get input device information. +static void rt_pa_set_source_info_and_quit( pa_context * /*c*/, const pa_source_info *i, + int eol, void *userdata ) { - if (eol) { - rt_pa_mainloop_api_quit(0); + PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); + if ( eol ) { + paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 0 ); return; } - PaDeviceInfo inf; - inf.info.name = pa_proplist_gets(i->proplist, "device.description"); - inf.info.probed = true; - inf.info.inputChannels = i->sample_spec.channels; - inf.info.preferredSampleRate = i->sample_spec.rate; - inf.info.isDefaultInput = (rt_pa_info.default_source_name == i->name); - inf.source_index = i->index; - inf.source_name = i->name; - for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) - inf.info.sampleRates.push_back( *sr ); - for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; - fm->rtaudio_format; ++fm ) - inf.info.nativeFormats |= fm->rtaudio_format; - for (size_t i=0; i < rt_pa_info.dev.size(); i++) - { - /* Attempt to match up sink and source records by device description. */ - if (rt_pa_info.dev[i].info.name == inf.info.name) { - rt_pa_info.dev[i].source_index = inf.source_index; - rt_pa_info.dev[i].source_name = inf.source_name; - rt_pa_info.dev[i].info.inputChannels = inf.info.inputChannels; - rt_pa_info.dev[i].info.isDefaultInput = inf.info.isDefaultInput; - /* Assume duplex channels are minimum of input and output channels. */ - /* Uncomment if we add support for DUPLEX - if (rt_pa_info.dev[i].sink_index > -1) { - rt_pa_info.dev[i].info.duplexChannels = - (inf.info.inputChannels < rt_pa_info.dev[i].info.outputChannels) - ? inf.info.inputChannels : rt_pa_info.dev[i].info.outputChannels; + std::string name = pa_proplist_gets( i->proplist, "device.description" ); + paProbeInfo->deviceNames.push_back( name ); + for ( size_t n=0; nrtDeviceList->size(); n++ ) { + if ( paProbeInfo->rtDeviceList->at(n).name == name ) { + // Check if we've already probed this as an output. + if ( !paProbeInfo->paDeviceList->at(n).sinkName.empty() ) { + // This must be a duplex device. Update the device info. + paProbeInfo->paDeviceList->at(n).sourceName = i->name; + paProbeInfo->rtDeviceList->at(n).inputChannels = i->sample_spec.channels; + paProbeInfo->rtDeviceList->at(n).isDefaultInput = ( paProbeInfo->defaultSourceName == i->name ); + paProbeInfo->rtDeviceList->at(n).duplexChannels = + (paProbeInfo->rtDeviceList->at(n).inputChannels < paProbeInfo->rtDeviceList->at(n).outputChannels) + ? paProbeInfo->rtDeviceList->at(n).inputChannels : paProbeInfo->rtDeviceList->at(n).outputChannels; } - */ - return; + return; // we already have this } } - /* try to ensure device #0 is the default */ - if (inf.info.isDefaultInput) - rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); - else - rt_pa_info.dev.push_back(inf); + + RtAudio::DeviceInfo info; + info.name = name; + info.inputChannels = i->sample_spec.channels; + info.preferredSampleRate = i->sample_spec.rate; + info.isDefaultInput = ( paProbeInfo->defaultSourceName == i->name ); + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + info.sampleRates.push_back( *sr ); + for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; fm->rtaudio_format; ++fm ) + info.nativeFormats |= fm->rtaudio_format; + info.ID = *(paProbeInfo->currentDeviceId); + *(paProbeInfo->currentDeviceId) = info.ID + 1; + paProbeInfo->rtDeviceList->push_back( info ); + + RtApiPulse::PaDeviceInfo painfo; + painfo.sourceName = i->name; + paProbeInfo->paDeviceList->push_back( painfo ); } -static void rt_pa_context_state_callback(pa_context *context, void *userdata) { - (void)userdata; - +// This is the initial function that is called when the callback is +// set. This one then calls the functions above. +static void rt_pa_context_state_callback( pa_context *context, void *userdata ) +{ + PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); auto state = pa_context_get_state(context); switch (state) { case PA_CONTEXT_CONNECTING: @@ -8687,19 +9250,18 @@ static void rt_pa_context_state_callback(pa_context *context, void *userdata) { break; case PA_CONTEXT_READY: - rt_pa_info.dev.clear(); - pa_context_get_server_info(context, rt_pa_set_server_info, NULL); - pa_context_get_sink_info_list(context, rt_pa_set_sink_info, NULL); - pa_context_get_source_info_list(context, rt_pa_set_source_info_and_quit, NULL); + pa_context_get_server_info( context, rt_pa_set_server_info, userdata ); // server info + pa_context_get_sink_info_list( context, rt_pa_set_sink_info, userdata ); // output info ... needs to be before input + pa_context_get_source_info_list( context, rt_pa_set_source_info_and_quit, userdata ); // input info break; case PA_CONTEXT_TERMINATED: - rt_pa_mainloop_api_quit(0); + paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 0 ); break; case PA_CONTEXT_FAILED: default: - rt_pa_mainloop_api_quit(1); + paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 1 ); } } @@ -8709,79 +9271,83 @@ RtApiPulse::~RtApiPulse() closeStream(); } -void RtApiPulse::collectDeviceInfo( void ) +void RtApiPulse :: probeDevices( void ) { + // See list of required functionality in RtApi::probeDevices(). + + pa_mainloop *ml = NULL; pa_context *context = NULL; - pa_mainloop *m = NULL; char *server = NULL; int ret = 1; - - if (!(m = pa_mainloop_new())) { - errorStream_ << "RtApiPulse::DeviceInfo pa_mainloop_new() failed."; + PaDeviceProbeInfo paProbeInfo; + if (!(ml = pa_mainloop_new())) { + errorStream_ << "RtApiPulse::probeDevices: pa_mainloop_new() failed."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto quit; } - rt_pa_mainloop_api = pa_mainloop_get_api(m); + paProbeInfo.paMainLoopApi = pa_mainloop_get_api( ml ); + paProbeInfo.currentDeviceId = ¤tDeviceId_; + paProbeInfo.paDeviceList = &paDeviceList_; + paProbeInfo.rtDeviceList = &deviceList_; - if (!(context = pa_context_new_with_proplist(rt_pa_mainloop_api, NULL, NULL))) { - errorStream_ << "pa_context_new() failed."; + if (!(context = pa_context_new_with_proplist( paProbeInfo.paMainLoopApi, NULL, NULL ))) { + errorStream_ << "RtApiPulse::probeDevices: pa_context_new() failed."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto quit; } - pa_context_set_state_callback(context, rt_pa_context_state_callback, NULL); - - if (pa_context_connect(context, server, PA_CONTEXT_NOFLAGS, NULL) < 0) { - errorStream_ << "RtApiPulse::DeviceInfo pa_context_connect() failed: " + pa_context_set_state_callback( context, rt_pa_context_state_callback, &paProbeInfo ); + + if (pa_context_connect( context, server, PA_CONTEXT_NOFLAGS, NULL ) < 0) { + errorStream_ << "RtApiPulse::probeDevices: pa_context_connect() failed: " << pa_strerror(pa_context_errno(context)); errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto quit; } - if (pa_mainloop_run(m, &ret) < 0) { - errorStream_ << "pa_mainloop_run() failed."; + if (pa_mainloop_run( ml, &ret ) < 0) { + errorStream_ << "RtApiPulse::probeDevices: pa_mainloop_run() failed."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto quit; } if (ret != 0) { - errorStream_ << "could not get server info."; + errorStream_ << "RtApiPulse::probeDevices: could not get server info."; errorText_ = errorStream_.str(); - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto quit; } + // Check for devices that have been unplugged. + unsigned int m; + for ( std::vector::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { + for ( m=0; m( user ); @@ -8804,240 +9370,7 @@ static void *pulseaudio_callback( void * user ) pthread_exit( NULL ); } -void RtApiPulse::closeStream( void ) -{ - PulseAudioHandle *pah = static_cast( stream_.apiHandle ); - - stream_.callbackInfo.isRunning = false; - if ( pah ) { - MUTEX_LOCK( &stream_.mutex ); - if ( stream_.state == STREAM_STOPPED ) { - pah->runnable = true; - pthread_cond_signal( &pah->runnable_cv ); - } - MUTEX_UNLOCK( &stream_.mutex ); - - pthread_join( pah->thread, 0 ); - if ( pah->s_play ) { - pa_simple_flush( pah->s_play, NULL ); - pa_simple_free( pah->s_play ); - } - if ( pah->s_rec ) - pa_simple_free( pah->s_rec ); - - pthread_cond_destroy( &pah->runnable_cv ); - delete pah; - stream_.apiHandle = 0; - } - - if ( stream_.userBuffer[0] ) { - free( stream_.userBuffer[0] ); - stream_.userBuffer[0] = 0; - } - if ( stream_.userBuffer[1] ) { - free( stream_.userBuffer[1] ); - stream_.userBuffer[1] = 0; - } - - stream_.state = STREAM_CLOSED; - stream_.mode = UNINITIALIZED; -} - -void RtApiPulse::callbackEvent( void ) -{ - PulseAudioHandle *pah = static_cast( stream_.apiHandle ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_LOCK( &stream_.mutex ); - while ( !pah->runnable ) - pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); - - if ( stream_.state != STREAM_RUNNING ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - MUTEX_UNLOCK( &stream_.mutex ); - } - - if ( stream_.state == STREAM_CLOSED ) { - errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " - "this shouldn't happen!"; - error( RtAudioError::WARNING ); - return; - } - - RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; - double streamTime = getStreamTime(); - RtAudioStreamStatus status = 0; - int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], - stream_.bufferSize, streamTime, status, - stream_.callbackInfo.userData ); - - if ( doStopStream == 2 ) { - abortStream(); - return; - } - - 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; - 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( RtAudioError::WARNING ); - } - } - - 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( RtAudioError::WARNING ); - } - if ( stream_.doConvertBuffer[INPUT] ) { - convertBuffer( stream_.userBuffer[INPUT], - stream_.deviceBuffer, - stream_.convertInfo[INPUT] ); - } - } - - unlock: - MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); - - if ( doStopStream == 1 ) - stopStream(); -} - -void RtApiPulse::startStream( void ) -{ - PulseAudioHandle *pah = static_cast( stream_.apiHandle ); - - if ( stream_.state == STREAM_CLOSED ) { - errorText_ = "RtApiPulse::startStream(): the stream is not open!"; - error( RtAudioError::INVALID_USE ); - return; - } - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiPulse::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; - } - - MUTEX_LOCK( &stream_.mutex ); - - #if defined( HAVE_GETTIMEOFDAY ) - gettimeofday( &stream_.lastTickTimestamp, NULL ); - #endif - - stream_.state = STREAM_RUNNING; - - pah->runnable = true; - pthread_cond_signal( &pah->runnable_cv ); - MUTEX_UNLOCK( &stream_.mutex ); -} - -void RtApiPulse::stopStream( void ) -{ - PulseAudioHandle *pah = static_cast( stream_.apiHandle ); - - if ( stream_.state == STREAM_CLOSED ) { - errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; - error( RtAudioError::INVALID_USE ); - return; - } - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; - } - - stream_.state = STREAM_STOPPED; - MUTEX_LOCK( &stream_.mutex ); - - if ( pah ) { - pah->runnable = false; - if ( pah->s_play ) { - int pa_error; - if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::stopStream: error draining output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; - } - } - } - - stream_.state = STREAM_STOPPED; - MUTEX_UNLOCK( &stream_.mutex ); -} - -void RtApiPulse::abortStream( void ) -{ - PulseAudioHandle *pah = static_cast( stream_.apiHandle ); - - if ( stream_.state == STREAM_CLOSED ) { - errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; - error( RtAudioError::INVALID_USE ); - return; - } - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; - } - - stream_.state = STREAM_STOPPED; - MUTEX_LOCK( &stream_.mutex ); - - if ( pah ) { - pah->runnable = false; - if ( pah->s_play ) { - int pa_error; - if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; - } - } - } - - stream_.state = STREAM_STOPPED; - MUTEX_UNLOCK( &stream_.mutex ); -} - -bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, +bool RtApiPulse::probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) @@ -9046,47 +9379,35 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned long bufferBytes = 0; pa_sample_spec ss; - if ( device >= rt_pa_info.dev.size() ) return false; + int deviceIdx = -1; + for ( unsigned int m=0; m( stream_.apiHandle ); + + stream_.callbackInfo.isRunning = false; + if ( pah ) { + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + + pthread_join( pah->thread, 0 ); + if ( pah->s_play ) { + pa_simple_flush( pah->s_play, NULL ); + pa_simple_free( pah->s_play ); + } + if ( pah->s_rec ) + pa_simple_free( pah->s_rec ); + + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + if ( stream_.userBuffer[0] ) { + free( stream_.userBuffer[0] ); + stream_.userBuffer[0] = 0; + } + if ( stream_.userBuffer[1] ) { + free( stream_.userBuffer[1] ); + stream_.userBuffer[1] = 0; + } + + clearStreamInfo(); +} + +void RtApiPulse::callbackEvent( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !pah->runnable ) + pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " + "this shouldn't happen!"; + error( RTAUDIO_WARNING ); + return; + } + + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], + stream_.bufferSize, streamTime, status, + stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + 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; + 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( RTAUDIO_WARNING ); + } + } + + 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( RTAUDIO_WARNING ); + } + if ( stream_.doConvertBuffer[INPUT] ) { + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + RtApi::tickStreamTime(); + + if ( doStopStream == 1 ) + stopStream(); +} + +RtAudioErrorType RtApiPulse::startStream( void ) +{ + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiPulse::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiPulse::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); + } + + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + MUTEX_LOCK( &stream_.mutex ); + + /* + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + */ + + stream_.state = STREAM_RUNNING; + + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); + return RTAUDIO_NO_ERROR; +} + +RtAudioErrorType RtApiPulse::stopStream( void ) +{ + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiPulse::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); + } + + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_SYSTEM_ERROR ); + } + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + return RTAUDIO_NO_ERROR; +} + +RtAudioErrorType RtApiPulse::abortStream( void ) +{ + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiPulse::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); + } + + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + return error( RTAUDIO_SYSTEM_ERROR ); + } + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + return RTAUDIO_NO_ERROR; +} + //******************** End of __LINUX_PULSE__ *********************// #endif @@ -9334,7 +9881,6 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, #include #include #include -#include #include #include @@ -9362,74 +9908,71 @@ RtApiOss :: ~RtApiOss() if ( stream_.state != STREAM_CLOSED ) closeStream(); } -unsigned int RtApiOss :: getDeviceCount( void ) +void RtApiOss :: probeDevices( void ) { + // See list of required functionality in RtApi::probeDevices(). + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { - errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; - error( RtAudioError::WARNING ); - return 0; + errorText_ = "RtApiOss::probeDevices: error opening '/dev/mixer'."; + error( RTAUDIO_SYSTEM_ERROR ); + return; } oss_sysinfo sysinfo; if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); - errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; - error( RtAudioError::WARNING ); - return 0; + errorText_ = "RtApiOss::probeDevices: error getting sysinfo, OSS version >= 4.0 is required."; + error( RTAUDIO_SYSTEM_ERROR ); + return; } - close( mixerfd ); - return sysinfo.numaudios; -} - -RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) -{ - RtAudio::DeviceInfo info; - info.probed = false; - - int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); - if ( mixerfd == -1 ) { - errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; - error( RtAudioError::WARNING ); - return info; - } - - oss_sysinfo sysinfo; - int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); - if ( result == -1 ) { - close( mixerfd ); - errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; - error( RtAudioError::WARNING ); - return info; - } - - unsigned nDevices = sysinfo.numaudios; + unsigned int nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { close( mixerfd ); - errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; - error( RtAudioError::INVALID_USE ); - return info; - } - - if ( device >= nDevices ) { - close( mixerfd ); - errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; - error( RtAudioError::INVALID_USE ); - return info; + deviceList_.clear(); + return; } oss_audioinfo ainfo; - ainfo.dev = device; - result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + unsigned int m, n; + std::vector deviceNames; + for ( n=0; n::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { + for ( m=0; m= nDevices ) { - // This should not happen because a check is made before this function is called. - close( mixerfd ); + std::string deviceName; + unsigned int m, device; + for ( m=0; mid[0] ); handle->id[0] = 0; @@ -9784,7 +10336,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned } stream_.sampleRate = sampleRate; - if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.deviceId[0] == device) { // We're doing duplex setup here. stream_.deviceFormat[0] = stream_.deviceFormat[1]; stream_.nDeviceChannels[0] = deviceChannels; @@ -9859,7 +10411,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned } } - stream_.device[mode] = device; + stream_.deviceId[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. @@ -9869,7 +10421,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; - if ( stream_.device[0] == device ) handle->id[0] = fd; + if ( stream_.deviceId[0] == device ) handle->id[0] = fd; } else { stream_.mode = mode; @@ -9952,7 +10504,7 @@ void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -9992,24 +10544,28 @@ void RtApiOss :: closeStream() stream_.deviceBuffer = 0; } - stream_.mode = UNINITIALIZED; - stream_.state = STREAM_CLOSED; + clearStreamInfo(); + //stream_.mode = UNINITIALIZED; + //stream_.state = STREAM_CLOSED; } -void RtApiOss :: startStream() +RtAudioErrorType RtApiOss :: startStream() { - verifyStream(); - if ( stream_.state == STREAM_RUNNING ) { - errorText_ = "RtApiOss::startStream(): the stream is already running!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_STOPPED ) { + if ( stream_.state == STREAM_RUNNING ) + errorText_ = "RtApiOss::startStream(): the stream is already running!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiOss::startStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); + /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif + */ stream_.state = STREAM_RUNNING; @@ -10020,15 +10576,17 @@ void RtApiOss :: startStream() OssHandle *handle = (OssHandle *) stream_.apiHandle; pthread_cond_signal( &handle->runnable ); + return RTAUDIO_NO_ERROR; } -void RtApiOss :: stopStream() +RtAudioErrorType RtApiOss :: stopStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiOss::stopStream(): the stream is closed!"; + return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); @@ -10036,7 +10594,7 @@ void RtApiOss :: stopStream() // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); - return; + return RTAUDIO_NO_ERROR; } int result = 0; @@ -10064,13 +10622,13 @@ void RtApiOss :: stopStream() result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); } } result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { - errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } @@ -10080,7 +10638,7 @@ void RtApiOss :: stopStream() if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { - errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } @@ -10090,17 +10648,18 @@ void RtApiOss :: stopStream() stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); - if ( result != -1 ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result != -1 ) return RTAUDIO_NO_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } -void RtApiOss :: abortStream() +RtAudioErrorType RtApiOss :: abortStream() { - verifyStream(); - if ( stream_.state == STREAM_STOPPED ) { - errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; - error( RtAudioError::WARNING ); - return; + if ( stream_.state != STREAM_RUNNING ) { + if ( stream_.state == STREAM_STOPPED ) + errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; + else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) + errorText_ = "RtApiOss::abortStream(): the stream is stopping or closed!"; + return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); @@ -10108,7 +10667,7 @@ void RtApiOss :: abortStream() // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); - return; + return RTAUDIO_NO_ERROR; } int result = 0; @@ -10116,7 +10675,7 @@ void RtApiOss :: abortStream() if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { - errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } @@ -10126,7 +10685,7 @@ void RtApiOss :: abortStream() if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { - errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } @@ -10136,8 +10695,8 @@ void RtApiOss :: abortStream() stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); - if ( result != -1 ) return; - error( RtAudioError::SYSTEM_ERROR ); + if ( result != -1 ) return RTAUDIO_SYSTEM_ERROR; + return error( RTAUDIO_SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() @@ -10155,7 +10714,7 @@ void RtApiOss :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return; } @@ -10225,7 +10784,7 @@ void RtApiOss :: callbackEvent() // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); // Continue on to input section. } } @@ -10252,7 +10811,7 @@ void RtApiOss :: callbackEvent() // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); goto unlock; } @@ -10306,36 +10865,24 @@ static void *ossCallbackHandler( void *ptr ) // This method can be modified to control the behavior of error // message printing. -void RtApi :: error( RtAudioError::Type type ) +RtAudioErrorType RtApi :: error( RtAudioErrorType type ) { - errorStream_.str(""); // clear the ostringstream + errorStream_.str(""); // clear the ostringstream to avoid repeated messages - 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; + // Don't output warnings if showWarnings_ is false + if ( type == RTAUDIO_WARNING && showWarnings_ == false ) return type; + + if ( errorCallback_ ) { + //const std::string errorMessage = errorText_; + //errorCallback_( type, errorMessage ); + errorCallback_( type, errorText_ ); } - - if ( type == RtAudioError::WARNING && showWarnings_ == true ) + else std::cerr << '\n' << errorText_ << "\n\n"; - else if ( type != RtAudioError::WARNING ) - throw( RtAudioError( errorText_, type ) ); + return type; } +/* void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { @@ -10343,6 +10890,7 @@ void RtApi :: verifyStream() error( RtAudioError::INVALID_USE ); } } +*/ void RtApi :: clearStreamInfo() { @@ -10359,9 +10907,9 @@ void RtApi :: clearStreamInfo() stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; - stream_.callbackInfo.errorCallback = 0; + stream_.callbackInfo.deviceDisconnected = false; for ( int i=0; i<2; i++ ) { - stream_.device[i] = 11111; + stream_.deviceId[i] = 11111; stream_.doConvertBuffer[i] = false; stream_.deviceInterleaved[i] = true; stream_.doByteSwap[i] = false; @@ -10395,7 +10943,7 @@ unsigned int RtApi :: formatBytes( RtAudioFormat format ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; - error( RtAudioError::WARNING ); + error( RTAUDIO_WARNING ); return 0; } @@ -10672,7 +11220,7 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info for (unsigned int i=0; i +#if defined(__APPLE__) +#include +#endif #if (TARGET_OS_IPHONE == 1) @@ -71,13 +74,19 @@ // flag can be defined (e.g. in your project file) to disable this behaviour. //#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES +// Default for Windows UWP is to enable a workaround to fix BLE-MIDI IN ports' +// wrong timestamps that occur at least in Windows 10 21H2; +// this flag can be defined (e.g. in your project file) +// to disable this behavior. +//#define RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS + // **************************************************************** // // // MidiInApi and MidiOutApi subclass prototypes. // // **************************************************************** // -#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) +#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(__WINDOWS_UWP__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) && !defined(__AMIDI__) #define __RTMIDI_DUMMY__ #endif @@ -254,6 +263,48 @@ class MidiOutWinMM: public MidiOutApi #endif +#if defined(__WINDOWS_UWP__) + +class MidiInWinUWP : public MidiInApi +{ +public: + MidiInWinUWP(const std::string& clientName, unsigned int queueSizeLimit); + ~MidiInWinUWP(void) override; + RtMidi::Api getCurrentApi(void) override { return RtMidi::WINDOWS_UWP; }; + void openPort(unsigned int portNumber, const std::string& portName) override; + void openVirtualPort(const std::string& portName) override; + void closePort(void) override; + void setClientName(const std::string& clientName) override; + void setPortName(const std::string& portName) override; + unsigned int getPortCount(void) override; + std::string getPortName(unsigned int portNumber) override; + double getMessage(std::vector* message) override; + +protected: + void initialize(const std::string& clientName) override; +}; + +class MidiOutWinUWP : public MidiOutApi +{ +public: + MidiOutWinUWP(const std::string& clientName); + ~MidiOutWinUWP(void) override; + RtMidi::Api getCurrentApi(void) override { return RtMidi::WINDOWS_UWP; }; + void openPort(unsigned int portNumber, const std::string& portName) override; + void openVirtualPort(const std::string& portName) override; + void closePort(void) override; + void setClientName(const std::string& clientName) override; + void setPortName(const std::string& portName) override; + unsigned int getPortCount(void) override; + std::string getPortName(unsigned int portNumber) override; + void sendMessage(const unsigned char* message, size_t size) override; + +protected: + void initialize(const std::string& clientName) override; +}; + +#endif + #if defined(__WEB_MIDI_API__) class MidiInWeb : public MidiInApi @@ -305,6 +356,67 @@ class MidiOutWeb: public MidiOutApi #endif +#if defined(__AMIDI__) + +#define LOG_TAG "RtMidi" +#include +#include +#include +#include +#include +#include +#include +#include + +class MidiInAndroid : public MidiInApi +{ + public: + MidiInAndroid(const std::string &/*clientName*/, unsigned int queueSizeLimit ); + ~MidiInAndroid( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::ANDROID_AMIDI; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + void onMidiMessage( uint8_t* data, double domHishResTimeStamp ); + + void initialize( const std::string& clientName ); + void connect(); + AMidiDevice* receiveDevice = NULL; + AMidiOutputPort* midiOutputPort = NULL; + pthread_t readThread; + std::atomic reading = ATOMIC_VAR_INIT(false); + static void* pollMidi(void* context); + double lastTime; +}; + +class MidiOutAndroid: public MidiOutApi +{ + public: + MidiOutAndroid( const std::string &clientName ); + ~MidiOutAndroid( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::ANDROID_AMIDI; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + void initialize( const std::string& clientName ); + void connect(); + AMidiDevice* sendDevice = NULL; + AMidiInputPort* midiInputPort = NULL; +}; + +#endif + #if defined(__RTMIDI_DUMMY__) class MidiInDummy: public MidiInApi @@ -378,8 +490,10 @@ const char* rtmidi_api_names[][2] = { { "alsa" , "ALSA" }, { "jack" , "Jack" }, { "winmm" , "Windows MultiMedia" }, - { "web" , "Web MIDI API" }, { "dummy" , "Dummy" }, + { "web" , "Web MIDI API" }, + { "winuwp" , "Windows UWP" }, + { "amidi" , "Android MIDI API" }, }; const unsigned int rtmidi_num_api_names = sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]); @@ -399,11 +513,17 @@ extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { #if defined(__WINDOWS_MM__) RtMidi::WINDOWS_MM, #endif +#if defined(__WINDOWS_UWP__) + RtMidi::WINDOWS_UWP, +#endif #if defined(__WEB_MIDI_API__) RtMidi::WEB_MIDI_API, #endif -#if defined(__RTMIDI_DUMMY__) - RtMidi::RTMIDI_DUMMY, +#if defined(__WEB_MIDI_API__) + RtMidi::WEB_MIDI_API, +#endif +#if defined(__AMIDI__) + RtMidi::ANDROID_AMIDI, #endif RtMidi::UNSPECIFIED, }; @@ -480,6 +600,10 @@ void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, un if ( api == WINDOWS_MM ) rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); #endif +#if defined(__WINDOWS_UWP__) + if (api == WINDOWS_UWP) + rtapi_ = new MidiInWinUWP(clientName, queueSizeLimit); +#endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiInCore( clientName, queueSizeLimit ); @@ -488,6 +612,10 @@ void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, un if ( api == WEB_MIDI_API ) rtapi_ = new MidiInWeb( clientName, queueSizeLimit ); #endif +#if defined(__AMIDI__) + if ( api == ANDROID_AMIDI ) + rtapi_ = new MidiInAndroid( clientName, queueSizeLimit ); +#endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); @@ -552,6 +680,10 @@ void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) if ( api == WINDOWS_MM ) rtapi_ = new MidiOutWinMM( clientName ); #endif +#if defined(__WINDOWS_UWP__) + if (api == WINDOWS_UWP) + rtapi_ = new MidiOutWinUWP(clientName); +#endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiOutCore( clientName ); @@ -560,6 +692,10 @@ void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) if ( api == WEB_MIDI_API ) rtapi_ = new MidiOutWeb( clientName ); #endif +#if defined(__AMIDI__) + if ( api == ANDROID_AMIDI ) + rtapi_ = new MidiOutAndroid( clientName ); +#endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiOutDummy( clientName ); @@ -1176,7 +1312,7 @@ unsigned int MidiInCore :: getPortCount() // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. -CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) +CFStringRef CreateEndpointName( MIDIEndpointRef endpoint, bool isExternal ) { CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); CFStringRef str; @@ -1186,13 +1322,10 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); if ( str != NULL ) { CFStringAppend( result, str ); - CFRelease( str ); } // some MIDI devices have a leading space in endpoint name. trim - CFStringRef space = CFStringCreateWithCString(NULL, " ", kCFStringEncodingUTF8); - CFStringTrim(result, space); - CFRelease(space); + CFStringTrim(result, CFSTR(" ")); MIDIEntityRef entity = 0; MIDIEndpointGetEntity( endpoint, &entity ); @@ -1206,7 +1339,6 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); if ( str != NULL ) { CFStringAppend( result, str ); - CFRelease( str ); } } // now consider the device's name @@ -1219,6 +1351,7 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); if ( CFStringGetLength( result ) == 0 ) { CFRelease( result ); + CFRetain( str ); return str; } if ( str != NULL ) { @@ -1226,10 +1359,10 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) // the endpoint name and just use the device name if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { CFRelease( result ); + CFRetain( str ); return str; } else { if ( CFStringGetLength( str ) == 0 ) { - CFRelease( str ); return result; } // does the entity name already start with the device name? @@ -1244,7 +1377,6 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) CFStringInsert( result, 0, str ); } - CFRelease( str ); } } return result; @@ -1252,7 +1384,7 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. -static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) +static CFStringRef CreateConnectedEndpointName( MIDIEndpointRef endpoint ) { CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); CFStringRef str; @@ -1279,11 +1411,12 @@ static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) if ( connObjectType == kMIDIObjectType_ExternalSource || connObjectType == kMIDIObjectType_ExternalDestination ) { // Connected to an external device's endpoint (10.3 and later). - str = EndpointName( (MIDIEndpointRef)(connObject), true ); + str = CreateEndpointName( (MIDIEndpointRef)(connObject), true ); } else { // Connected to an external device (10.2) (or something else, catch- str = NULL; MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str ); + if ( str ) CFRetain ( str ); } if ( str != NULL ) { if ( anyStrings ) @@ -1304,7 +1437,7 @@ static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) CFRelease( result ); // Here, either the endpoint had no connections, or we failed to obtain names - return EndpointName( endpoint, false ); + return CreateEndpointName( endpoint, false ); } std::string MidiInCore :: getPortName( unsigned int portNumber ) @@ -1324,7 +1457,7 @@ std::string MidiInCore :: getPortName( unsigned int portNumber ) } portRef = MIDIGetSource( portNumber ); - nameRef = ConnectedEndpointName( portRef ); + nameRef = CreateConnectedEndpointName( portRef ); CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); CFRelease( nameRef ); @@ -1411,7 +1544,7 @@ std::string MidiOutCore :: getPortName( unsigned int portNumber ) } portRef = MIDIGetDestination( portNumber ); - nameRef = ConnectedEndpointName(portRef); + nameRef = CreateConnectedEndpointName(portRef); CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); CFRelease( nameRef ); @@ -2728,7 +2861,7 @@ void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*port // Allocate and init the sysex buffers. data->sysexBuffer.resize( inputData_.bufferCount ); - for ( int i=0; i < inputData_.bufferCount; ++i ) { + for ( unsigned int i=0; i < inputData_.bufferCount; ++i ) { data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; data->sysexBuffer[i]->lpData = new char[ inputData_.bufferSize ]; data->sysexBuffer[i]->dwBufferLength = inputData_.bufferSize; @@ -2782,7 +2915,7 @@ void MidiInWinMM :: closePort( void ) midiInReset( data->inHandle ); midiInStop( data->inHandle ); - for ( int i=0; i < data->sysexBuffer.size(); ++i ) { + for ( size_t i=0; i < data->sysexBuffer.size(); ++i ) { int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); delete [] data->sysexBuffer[i]->lpData; delete [] data->sysexBuffer[i]; @@ -3023,7 +3156,7 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) for ( unsigned int i=0; i +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Devices::Enumeration; +using namespace Windows::Devices::Midi; +using namespace Windows::Storage::Streams; +using namespace Windows::Security::Cryptography; + +// Class for initializing C++/WinRT +class UWPMidiInit +{ +public: + UWPMidiInit() + { + winrt::init_apartment(); + } +}; + +// Class for handling UWP MIDI +class UWPMidiClass +{ +public: + // Structure to store MIDI port name and ID + struct port + { + std::string name; + std::wstring id; + std::string hex_id; + std::string display_name; + }; + + UWPMidiClass(MidiApi& midi_api) : + midi_api_(midi_api) + { + } + + ~UWPMidiClass() + { + close(); + } + + // Initialize for MIDI IN + void in_init(MidiInApi::RtMidiInData* input_data) + { + input_data_ = input_data; + + try + { + ports_ = list_ports(MidiInPort::GetDeviceSelector()); + } + catch (hresult_error const& ex) + { + raise_hresult_error("UWPMidiClass::in_init: ", ex); + } + sort_display_name(ports_); + } + + // Initialize for MIDI OUT + void out_init() + { + try + { + ports_ = list_ports(MidiOutPort::GetDeviceSelector()); + fix_display_name(list_ports(MidiInPort::GetDeviceSelector()), ports_); + } + catch (hresult_error const& ex) + { + raise_hresult_error("UWPMidiClass::out_init: ", ex); + } + sort_display_name(ports_); + } + + size_t get_num_ports() + { + return ports_.size(); + } + + std::string get_port_name(size_t n) + { + return ports_[n].display_name; + } + + bool in_open(size_t port_number); + bool out_open(size_t port_number); + void close(); + + void midi_in_callback(const MidiInPort&, const MidiMessageReceivedEventArgs& e); + bool send_buffer(const unsigned char* buff, size_t len); + + // Raise RtMidi error for hresult error + void raise_hresult_error(std::string_view message, hresult_error const& ex) + { + std::ostringstream ss; + ss << message << "exception HRESULT 0x" << std::hex << ex.code() << ", " + << utf16_to_utf8(static_cast(ex.message())) + << "\n"; + midi_api_.error(RtMidiError::DRIVER_ERROR, ss.str()); + } + + // Mutex for MIDI port open/close + std::mutex mtx_open_close_; + // Mutex for MIDI IN message queue access + std::mutex mtx_queue_; + +private: + std::vector list_ports(winrt::hstring device_selector); + void fix_display_name(const std::vector& in_ports, + std::vector& out_ports); + void sort_display_name(std::vector& ports); + std::string utf16_to_utf8(const std::wstring_view wstr); + + template + IMidiPort_T open(size_t port_number); + + // MidiApi class + MidiApi& midi_api_; + + // List of MIDI ports + std::vector ports_; + // MIDI IN port + MidiInPort in_port_{ nullptr }; + // MIDI OUT port + IMidiOutPort out_port_{ nullptr }; + // Backup initial MessageReceived event token + winrt::event_token before_token_; + // Input data + MidiInApi::RtMidiInData* input_data_{ nullptr }; + // Last timestamp + std::chrono::duration last_time_{ 0 }; + + // C++/WinRT initializer + static UWPMidiInit uwp_midi_init_; + // Regex pattern to extract 8 hex digits from UWP MIDI ID string + static const std::wregex hex_id_pattern_; + +#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS + // QueryPerformanceFrequency + LONGLONG qpc_freq_{ 0 }; + // Last QueryPerformanceCounter + LONGLONG before_qpc_; + // Weather overflow low occurred or not + bool b_overflow_low_{ false }; + + // BLE-MIDI timestamp periods + inline constexpr static std::chrono::duration ble_midi_period_low_{ std::chrono::milliseconds{128} }; + inline constexpr static std::chrono::duration ble_midi_period_high_{ std::chrono::milliseconds{8192} }; + // QPC threshold 4096 ms + inline constexpr static LONGLONG qpc_threshold_{ 4096 }; + + // Regex pattern to detect BLE-MIDI IN + static const std::wregex ble_midi_pattern_; +#endif +}; + +// C++/WinRT initializer +UWPMidiInit UWPMidiClass::uwp_midi_init_; +// Regex pattern to extract 8 hex digits from UWP MIDI ID string +const std::wregex UWPMidiClass::hex_id_pattern_{ std::wregex(L"#MIDII_([0-9A-F]{8})\\..+#") }; + +#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS +const std::wregex UWPMidiClass::ble_midi_pattern_{ std::wregex(L"#MIDII_[0-9A-F]{8}\\.BLE[0-9]{2}#") }; +#endif + +// Find and create a list of UWP MIDI ports +std::vector UWPMidiClass::list_ports(winrt::hstring device_selector) +{ + const auto devs{ DeviceInformation::FindAllAsync(device_selector).get() }; + + std::vector retval; + for (const auto& d : devs) + { + port p; + p.name = utf16_to_utf8(d.Name()); + p.id = d.Id(); + + std::wsmatch m; + if (std::regex_search(p.id, m, hex_id_pattern_)) + { + // Ordinary MIDI ports + // Append hex digits extracted from the UWP MIDI ID string to the port name. + p.hex_id = utf16_to_utf8(m[1].str()); + + std::ostringstream ss; + ss << p.name + << " [ " + << p.hex_id + << " ]"; + p.display_name = ss.str(); + } + else + { + // Microsoft GS Wavetable Synth etc. + // Unable to extract hex digits from UWP MIDI ID string. + // Use the device name as the port name. + p.display_name = p.name; + } + + retval.push_back(p); + } + return retval; +} + +// Fix MIDI OUT port names starting with `MIDI` to MIDI IN port names with similar ID strings +void UWPMidiClass::fix_display_name(const std::vector& in_ports, + std::vector& out_ports) +{ + for (auto& outp : out_ports) + { + if (outp.hex_id.empty() || + std::string_view{ outp.name }.substr(0, 4) != "MIDI") + continue; + + for (const auto& inp : in_ports) + { + if (outp.hex_id == inp.hex_id) + { + outp.display_name = inp.display_name; + break; + } + } + } +} + +void UWPMidiClass::sort_display_name(std::vector& ports) +{ + std::sort(ports.begin(), ports.end(), + [](const auto& lhs, const auto& rhs) + { + return lhs.display_name < rhs.display_name; + }); +} + +std::string UWPMidiClass::utf16_to_utf8(const std::wstring_view wstr) +{ + auto len{ WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr) }; + std::string u8str(len, '\0'); + if (len) + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), u8str.data(), len, nullptr, nullptr); + return u8str; +} + +// Open MIDI IN/OUT port +template +IMidiPort_T UWPMidiClass::open(size_t port_number) +{ + try + { + auto async{ MidiPort_T::FromIdAsync(ports_[port_number].id) }; + // Timeout 3 seconds + if (async.wait_for(std::chrono::seconds(3)) == AsyncStatus::Completed) + return async.GetResults(); + } + catch (hresult_error const& ex) + { + raise_hresult_error("UWPMidiClass::open: ", ex); + } + return nullptr; +} + +// Open MIDI IN port +bool UWPMidiClass::in_open(size_t port_number) +{ + if (in_port_) + in_port_.Close(); + + in_port_ = open(port_number); + if (!in_port_) + return false; + +#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS + std::wsmatch m; + if (std::regex_search(ports_[port_number].id, m, ble_midi_pattern_)) + { + // BLE-MIDI IN port + LARGE_INTEGER li; + if (::QueryPerformanceFrequency(&li)) + qpc_freq_ = li.QuadPart; + } +#endif + + try + { + before_token_ = in_port_.MessageReceived({ this, &UWPMidiClass::midi_in_callback }); + } + catch (hresult_error const& ex) + { + raise_hresult_error("UWPMidiClass::in_open: ", ex); + } + + return true; +} + +// Open MIDI Out port +bool UWPMidiClass::out_open(size_t port_number) +{ + if (out_port_) + out_port_.Close(); + + out_port_ = open(port_number); + if (!out_port_) + return false; + + return true; +} + +// Close MIDI IN/OUT port +void UWPMidiClass::close() +{ + if (in_port_) + { + if (before_token_) + in_port_.MessageReceived(before_token_); + + in_port_.Close(); + in_port_ = nullptr; + } + if (out_port_) + { + out_port_.Close(); + out_port_ = nullptr; + } +} + +// MessageReceived event handler +void UWPMidiClass::midi_in_callback(const MidiInPort&, const MidiMessageReceivedEventArgs& e) +{ +#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS + LARGE_INTEGER qpc; + if (qpc_freq_) + { + if (!::QueryPerformanceCounter(&qpc)) + qpc_freq_ = 0; + } +#endif + + const auto& m{ e.Message() }; + if (!m) + return; + + MidiInApi::MidiMessage message; + const std::chrono::duration duration{ m.Timestamp() }; + + // Calculate time stamp. + if (input_data_->firstMessage == true) + { + message.timeStamp = 0.0; + input_data_->firstMessage = false; + last_time_ = duration; + +#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS + if (qpc_freq_) + before_qpc_ = qpc.QuadPart; +#endif + } + else + { + auto delta{ duration - last_time_ }; + +#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS + if (qpc_freq_) + { + if (b_overflow_low_) + { + if (delta >= ble_midi_period_low_) + { + // Fix after overflow low + // https://github.com/trueroad/BLE_MIDI_packet_data_set#page-7-overflow-both + delta -= ble_midi_period_low_; + b_overflow_low_ = false; + } + } + else + { + if ((ble_midi_period_high_ - ble_midi_period_low_) < delta && delta < ble_midi_period_high_ && + ((before_qpc_ - qpc.QuadPart) * 1000 / qpc_freq_) < qpc_threshold_) + { + // Fix overflow low + // https://github.com/trueroad/BLE_MIDI_packet_data_set#page-7-overflow-low + delta = delta - ble_midi_period_high_ + ble_midi_period_low_; + b_overflow_low_ = true; + } + } + + before_qpc_ = qpc.QuadPart; + } +#endif + + const std::chrono::duration sec{ delta }; + message.timeStamp = sec.count(); + } + + if (((input_data_->ignoreFlags & 0x01) && + (m.Type() == MidiMessageType::SystemExclusive || m.Type() == MidiMessageType::EndSystemExclusive)) || + ((input_data_->ignoreFlags & 0x02) && + (m.Type() == MidiMessageType::MidiTimeCode || m.Type() == MidiMessageType::TimingClock)) || + ((input_data_->ignoreFlags & 0x04) && + m.Type() == MidiMessageType::ActiveSensing)) + { + return; + } + + const auto& raw_data{ m.RawData() }; + const size_t len{ raw_data.Length() }; + + if (len) + message.bytes.assign(raw_data.data(), raw_data.data() + len); + + last_time_ = duration; + + if (input_data_->usingCallback) + { + (input_data_->userCallback)(message.timeStamp, &message.bytes, input_data_->userData); + } + else + { + std::lock_guard lock(mtx_queue_); + + if (!input_data_->queue.push(message)) + { + std::cerr << "\nMidiInWinUWP: message queue limit reached!!\n\n"; + } + } +} + +// Send MIDI message +bool UWPMidiClass::send_buffer(const unsigned char* buff, size_t len) +{ + if (!out_port_) + return false; + + try + { + out_port_.SendBuffer(CryptographicBuffer::CreateFromByteArray(array_view(buff, buff + len))); + } + catch (hresult_error const& ex) + { + raise_hresult_error("UWPMidiClass::send_buffer: ", ex); + } + + return true; +} + +//*********************************************************************// +// API: Windows UWP +// Class Definitions: MidiInWinUWP +//*********************************************************************// + +MidiInWinUWP::MidiInWinUWP(const std::string& clientName, unsigned int queueSizeLimit) + : MidiInApi(queueSizeLimit) +{ + MidiInWinUWP::initialize(clientName); +} + +MidiInWinUWP :: ~MidiInWinUWP() +{ + // Close a connection if it exists. + MidiInWinUWP::closePort(); + + // Cleanup. + UWPMidiClass *data = static_cast (apiData_); + delete data; +} + +void MidiInWinUWP::initialize(const std::string& /*clientName*/) +{ + // Save our api-specific connection information. + UWPMidiClass* data{ new UWPMidiClass(*this) }; + data->in_init(&inputData_); + apiData_ = static_cast(data); + + // We'll issue a warning here if no devices are available but not + // throw an error since the user can plugin something later. + const auto nDevices{ data->get_num_ports() }; + if (nDevices == 0) + { + errorString_ = "MidiInWinUWP::initialize: no MIDI input devices currently available."; + error(RtMidiError::WARNING, errorString_); + } +} + +void MidiInWinUWP::openPort(unsigned int portNumber, const std::string&/*portName*/) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + std::lock_guard lock(data->mtx_open_close_); + + if (connected_) + { + errorString_ = "MidiInWinUWP::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + if (data->get_num_ports() == 0) + { + errorString_ = "MidiInWinUWP::openPort: no MIDI input sources found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + if (portNumber >= data->get_num_ports()) + { + std::ostringstream ost; + ost << "MidiInWinUWP::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + if (!data->in_open(portNumber)) + { + errorString_ = "MidiInWinUWP::openPort: error creating Windows UWP MIDI input port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + connected_ = true; +} + +void MidiInWinUWP::openVirtualPort(const std::string&/*portName*/) +{ + // This function cannot be implemented for the Windows UWP MIDI API. + errorString_ = "MidiInWinUWP::openVirtualPort: cannot be implemented in Windows UWP MIDI API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiInWinUWP::closePort(void) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + std::lock_guard lock(data->mtx_open_close_); + + if (connected_) + { + data->close(); + connected_ = false; + } +} + +void MidiInWinUWP::setClientName(const std::string&) +{ + errorString_ = "MidiInWinUWP::setClientName: this function is not implemented for the WINDOWS_UWP API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiInWinUWP::setPortName(const std::string&) +{ + errorString_ = "MidiInWinUWP::setPortName: this function is not implemented for the WINDOWS_UWP API!"; + error(RtMidiError::WARNING, errorString_); +} + +unsigned int MidiInWinUWP::getPortCount() +{ + UWPMidiClass* data{ static_cast(apiData_) }; + return static_cast(data->get_num_ports()); +} + +std::string MidiInWinUWP::getPortName(unsigned int portNumber) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + + const auto nDevices{ data->get_num_ports() }; + if (portNumber >= nDevices) + { + std::ostringstream ost; + ost << "MidiInWinUWP::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + return ""; + } + + return data->get_port_name(portNumber); +} + +double MidiInWinUWP::getMessage(std::vector* message) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + std::lock_guard lock(data->mtx_queue_); + + return MidiInApi::getMessage(message); +} + +//*********************************************************************// +// API: Windows UWP +// Class Definitions: MidiOutWinUWP +//*********************************************************************// + +MidiOutWinUWP::MidiOutWinUWP(const std::string& clientName) : MidiOutApi() +{ + MidiOutWinUWP::initialize(clientName); +} + +MidiOutWinUWP :: ~MidiOutWinUWP() +{ + // Close a connection if it exists. + MidiOutWinUWP::closePort(); + + // Cleanup. + UWPMidiClass* data = static_cast (apiData_); + delete data; +} + +void MidiOutWinUWP::initialize(const std::string& /*clientName*/) +{ + // Save our api-specific connection information. + UWPMidiClass* data{ new UWPMidiClass(*this) }; + data->out_init(); + apiData_ = static_cast(data); + + // We'll issue a warning here if no devices are available but not + // throw an error since the user can plug something in later. + const auto nDevices{ data->get_num_ports() }; + if (nDevices == 0) + { + errorString_ = "MidiOutWinUWP::initialize: no MIDI output devices currently available."; + error(RtMidiError::WARNING, errorString_); + } +} + +unsigned int MidiOutWinUWP::getPortCount() +{ + UWPMidiClass* data{ static_cast(apiData_) }; + return static_cast(data->get_num_ports()); +} + +std::string MidiOutWinUWP::getPortName(unsigned int portNumber) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + + const auto nDevices{ data->get_num_ports() }; + if (portNumber >= nDevices) + { + std::ostringstream ost; + ost << "MidiOutWinUWP::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + return ""; + } + + return data->get_port_name(portNumber); +} + +void MidiOutWinUWP::openPort(unsigned int portNumber, const std::string&/*portName*/) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + std::lock_guard lock(data->mtx_open_close_); + + if (connected_) + { + errorString_ = "MidiOutWinUWP::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + if (data->get_num_ports() == 0) + { + errorString_ = "MidiOutWinUWP::openPort: no MIDI output destinations found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + if (portNumber >= data->get_num_ports()) + { + std::ostringstream ost; + ost << "MidiOutWinUWP::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + if (!data->out_open(portNumber)) + { + errorString_ = "MidiOutWinUWP::openPort: error creating Windows UWP MIDI output port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + connected_ = true; +} + +void MidiOutWinUWP::closePort(void) +{ + UWPMidiClass* data{ static_cast(apiData_) }; + std::lock_guard lock(data->mtx_open_close_); + + if (connected_) + { + data->close(); + connected_ = false; + } +} + +void MidiOutWinUWP::setClientName(const std::string&) +{ + errorString_ = "MidiOutWinUWP::setClientName: this function is not implemented for the WINDOWS_UWP API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutWinUWP::setPortName(const std::string&) +{ + errorString_ = "MidiOutWinUWP::setPortName: this function is not implemented for the WINDOWS_UWP API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutWinUWP::openVirtualPort(const std::string&/*portName*/) +{ + // This function cannot be implemented for the Windows UWP MIDI API. + errorString_ = "MidiOutWinUWP::openVirtualPort: cannot be implemented in Windows UWP MIDI API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutWinUWP::sendMessage(const unsigned char* message, size_t size) +{ + if (!connected_) + return; + + if (size == 0) + { + errorString_ = "MidiOutWinUWP::sendMessage: message argument is empty!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + UWPMidiClass* data{ static_cast(apiData_) }; + if (!data->send_buffer(message, size)) + { + errorString_ = "MidiOutWinUWP::sendMessage: error sending message."; + error(RtMidiError::DRIVER_ERROR, errorString_); + } +} + +#endif // __WINDOWS_UWP__ + + //*********************************************************************// // API: UNIX JACK // @@ -3092,6 +3986,7 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) #include #include #include +#include #ifdef HAVE_SEMAPHORE #include #endif @@ -3608,7 +4503,7 @@ void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) return; while ( jack_ringbuffer_write_space(data->buff) < sizeof(nBytes) + size ) - pthread_yield(); + sched_yield(); // Write full message to buffer jack_ringbuffer_write( data->buff, ( char * ) &nBytes, sizeof( nBytes ) ); @@ -3723,7 +4618,7 @@ std::string WebMidiAccessShim::getPortName( unsigned int portNumber, bool isInpu var ret = _malloc(length); stringToUTF8(port.name, ret, length); return ret; - }, portNumber, isInput, &ret ); + }, portNumber, isInput); if (ret == nullptr) return ""; std::string s = ret; @@ -3778,6 +4673,7 @@ void MidiInWeb::openPort( unsigned int portNumber, const std::string &portName ) }; }, portNumber, &inputData_ ); open_port_number = portNumber; + connected_ = true; } void MidiInWeb::openVirtualPort( const std::string &portName ) @@ -3803,6 +4699,7 @@ void MidiInWeb::closePort( void ) input.onmidimessage = null; }, open_port_number ); open_port_number = -1; + connected_ = false; } void MidiInWeb::setClientName( const std::string &clientName ) @@ -3862,6 +4759,7 @@ void MidiOutWeb::openPort( unsigned int portNumber, const std::string &portName // In Web MIDI API world, there is no step to open a port. open_port_number = portNumber; + connected_ = true; } void MidiOutWeb::openVirtualPort( const std::string &portName ) @@ -3876,6 +4774,7 @@ void MidiOutWeb::closePort( void ) { // there is really nothing to do for output at JS side. open_port_number = -1; + connected_ = false; } void MidiOutWeb::setClientName( const std::string &clientName ) @@ -3932,3 +4831,434 @@ void MidiOutWeb::initialize( const std::string& clientName ) } #endif // __WEB_MIDI_API__ + + +//*********************************************************************// +// API: ANDROID AMIDI +// +// Written by Yellow Labrador, May 2023. +// https://github.com/YellowLabrador/rtmidi +// *********************************************************************// + +#if defined(__AMIDI__) + +#include + +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +static std::string androidClientName; +static std::vector androidMidiDevices; + +//*********************************************************************// +// API: Android AMIDI +// Class Definitions: MidiInAndroid +//*********************************************************************// + +static JNIEnv* androidGetThreadEnv() { + // Every Android app has only one JVM. Calling JNI_GetCreatedJavaVMs + // will retrieve the JVM running the app. + jsize jvmsFound = 0; + JavaVM jvms[1]; + JavaVM* pjvms = jvms; + jint result = JNI_GetCreatedJavaVMs(&pjvms, 1, &jvmsFound); + + // Something went terribly wrong, no JVM was found + if (jvmsFound != 1 || result != JNI_OK) { + LOGE("No JVM found"); + return NULL; + } + + // Get the JNIEnv for the current thread + JNIEnv* env = NULL; + int rc = pjvms->GetEnv((void**)&env, JNI_VERSION_1_6); + + // The current thread was not attached to the JVM. Add it to the JVM + if (rc == JNI_EDETACHED) { + pjvms->AttachCurrentThreadAsDaemon(&env, NULL); + } + + // Neither way to retrieve the JNIEnv worked + if (env == NULL) { + LOGE("Unable to retrieve JNI environment"); + } + + return env; +} + +static jobject androidGetContext(JNIEnv *env) { + auto activityThread = env->FindClass("android/app/ActivityThread"); + auto currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); + auto at = env->CallStaticObjectMethod(activityThread, currentActivityThread); + if (at == NULL) { + LOGE("Unable to locate the global ActivityThread"); + return NULL; + } + + auto getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;"); + auto context = env->CallObjectMethod(at, getApplication); + if (context == NULL) { + LOGE("Application context was NULL"); + } + + return context; +} + +static jobject androidGetMidiManager(JNIEnv *env, jobject context) { + // MidiManager midiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE); + auto contextClass = env->FindClass("android/content/Context"); + auto getServiceMethod = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + return env->CallObjectMethod(context, getServiceMethod, env->NewStringUTF("midi")); +} + +static void androidRefreshMidiDevices(JNIEnv *env, jobject context, bool isOutput) { + // Remove all midi devices + for (jobject jMidiDevice : androidMidiDevices) { + env->DeleteGlobalRef(jMidiDevice); + } + androidMidiDevices.clear(); + + auto midiService = androidGetMidiManager(env, context); + + // MidiDeviceInfo[] devInfos = mMidiManager.getDevices(); + auto midiMgrClass = env->FindClass("android/media/midi/MidiManager"); + auto getDevicesMethod = env->GetMethodID(midiMgrClass, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;"); + auto jDevices = (jobjectArray) env->CallObjectMethod(midiService, getDevicesMethod); + + auto deviceInfoClass = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto getInputPortCountMethod = env->GetMethodID(deviceInfoClass, "getInputPortCount", "()I"); + auto getOutputPortCountMethod = env->GetMethodID(deviceInfoClass, "getOutputPortCount", "()I"); + + jsize len = env->GetArrayLength((jarray)jDevices); + for (int i=0; iGetObjectArrayElement(jDevices, i); + + int numPorts = env->CallIntMethod(jDeviceInfo, isOutput ? getOutputPortCountMethod : getInputPortCountMethod); + if (numPorts > 0) { + androidMidiDevices.push_back(env->NewGlobalRef(jDeviceInfo)); + } + } +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_yellowlab_rtmidi_MidiDeviceOpenedListener_midiDeviceOpened(JNIEnv *env, jclass clazz, + jobject midi_device, jlong targetPtr, jboolean isOutput) { + if (isOutput) { + auto midiOut = reinterpret_cast(targetPtr); + AMidiDevice_fromJava(env, midi_device, &midiOut->sendDevice); + AMidiInputPort_open(midiOut->sendDevice, 0, &midiOut->midiInputPort); + } else { + auto midiIn = reinterpret_cast(targetPtr); + AMidiDevice_fromJava(env, midi_device, &midiIn->receiveDevice); + AMidiOutputPort_open(midiIn->receiveDevice, 0, &midiIn->midiOutputPort); + pthread_create(&midiIn->readThread, NULL, MidiInAndroid::pollMidi, midiIn); + } +} + +static void androidOpenDevice(jobject deviceInfo, void* target, bool isOutput) { + auto env = androidGetThreadEnv(); + auto context = androidGetContext(env); + auto midiMgr = androidGetMidiManager(env, context); + + // openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler) + auto midiMgrClass = env->GetObjectClass(midiMgr); + auto openDevicesMethod = env->GetMethodID(midiMgrClass, "openDevice", "(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V"); + + auto handlerClass = env->FindClass("android/os/Handler"); + auto handlerCtor = env->GetMethodID(handlerClass, "", "()V"); + auto handler = env->NewObject(handlerClass, handlerCtor); + + auto listenerClass = env->FindClass("com/yellowlab/rtmidi/MidiDeviceOpenedListener"); + if (!listenerClass) { + LOGE("Midi listener class not found com.yellowlab.rtmidi.MidiDeviceOpenedListener. Did you forget to add it to your APK?"); + return; + } + + auto targetPtr = reinterpret_cast(target); + auto listenerCtor = env->GetMethodID(listenerClass, "", "(JZ)V"); + auto listener = env->NewObject(listenerClass, listenerCtor, targetPtr, isOutput); + + env->CallVoidMethod(midiMgr, openDevicesMethod, deviceInfo, listener, handler); + env->DeleteLocalRef(handler); +} + +static std::string androidPortName(JNIEnv *env, unsigned int portNumber) { + if (portNumber >= androidMidiDevices.size()) { + LOGE("androidPortName: Invalid port number"); + return ""; + } + + // String deviceName = devInfo.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME); + auto deviceInfoClass = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto getPropsMethod = env->GetMethodID(deviceInfoClass, "getProperties", "()Landroid/os/Bundle;"); + auto bundle = env->CallObjectMethod(androidMidiDevices[portNumber], getPropsMethod); + + auto bundleClass = env->FindClass("android/os/Bundle"); + auto getStringMethod = env->GetMethodID(bundleClass, "getString", "(Ljava/lang/String;)Ljava/lang/String;"); + auto jPortName = (jstring) env->CallObjectMethod(bundle, getStringMethod, env->NewStringUTF("name")); + + auto portNameChars = env->GetStringUTFChars(jPortName, NULL); + auto name = std::string(portNameChars); + env->ReleaseStringUTFChars(jPortName, portNameChars); + + return name; +} + +MidiInAndroid :: MidiInAndroid( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) { + MidiInAndroid::initialize( clientName ); +} + +void MidiInAndroid :: initialize( const std::string& clientName ) { + androidClientName = clientName; + connect(); +} + +void MidiInAndroid :: connect() { + auto env = androidGetThreadEnv(); + auto context = androidGetContext(env); + androidRefreshMidiDevices(env, context, true); + + env->DeleteLocalRef(context); +} + +MidiInAndroid :: ~MidiInAndroid() { + auto env = androidGetThreadEnv(); + + // Remove all midi devices + for (jobject jMidiDevice : androidMidiDevices) { + env->DeleteGlobalRef(jMidiDevice); + } + androidMidiDevices.clear(); + + androidClientName = ""; +} + +void MidiInAndroid :: openPort(unsigned int portNumber, const std::string &portName) { + if (portNumber >= androidMidiDevices.size()) { + errorString_ = "MidiInAndroid::openPort: Invalid port number"; + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + + return; + } + + if (reading) { + errorString_ = "MidiInAndroid::openPort: A port is already open"; + error( RtMidiError::INVALID_USE, errorString_ ); + + return; + } + + androidOpenDevice(androidMidiDevices[portNumber], this, false); +} + +void MidiInAndroid :: openVirtualPort(const std::string &portName) { + errorString_ = "MidiInAndroid::openVirtualPort: this function is not implemented for the Android API!"; + error( RtMidiError::WARNING, errorString_ ); +} + +unsigned int MidiInAndroid :: getPortCount() { + connect(); + return androidMidiDevices.size(); +} + +std::string MidiInAndroid :: getPortName(unsigned int portNumber) { + auto env = androidGetThreadEnv(); + return androidPortName(env, portNumber); +} + +void MidiInAndroid :: closePort() { + // Don't try to close a port before it was open + if (!reading) { + return; + } + + reading = false; + pthread_join(readThread, NULL); + + AMidiDevice_release(receiveDevice); + receiveDevice = NULL; + midiOutputPort = NULL; +} + +void MidiInAndroid:: setClientName(const std::string& clientName) { + androidClientName = clientName; +} + +void MidiInAndroid :: setPortName(const std::string &portName) { + errorString_ = "MidiInAndroid::setPortName: this function is not implemented for the Android API!"; + error( RtMidiError::WARNING, errorString_ ); +} + +void* MidiInAndroid :: pollMidi(void* context) { + auto self = (MidiInAndroid*) context; + self->reading = true; + + const size_t MAX_BYTES_TO_RECEIVE = 128; + uint8_t incomingMessage[MAX_BYTES_TO_RECEIVE]; + + while (self->reading) { + // AMidiOutputPort_receive is non-blocking, must poll with some sleep + usleep(2000); + auto ignoreFlags = self->inputData_.ignoreFlags; + bool& continueSysex = self->inputData_.continueSysex; + + int32_t opcode; + size_t numBytesReceived; + int64_t timestamp; + ssize_t numMessagesReceived = AMidiOutputPort_receive( + self->midiOutputPort, &opcode, incomingMessage, MAX_BYTES_TO_RECEIVE, + &numBytesReceived, ×tamp); + + if (numMessagesReceived < 0) { + self->errorString_ = "MidiInAndroid::pollMidi: error receiving MIDI data"; + self->error( RtMidiError::SYSTEM_ERROR, self->errorString_ ); + self->reading = false; + break; + } + + switch (incomingMessage[0]) { + case 0xF0: + // Start of a SysEx message + continueSysex = incomingMessage[numBytesReceived - 1] != 0xF7; + if (ignoreFlags & 0x01) continue; + break; + case 0xF1: + case 0xF8: + // MIDI Time Code or Timing Clock message + if (ignoreFlags & 0x02) continue; + break; + case 0xFE: + // Active Sensing message + if (ignoreFlags & 0x04) continue; + break; + default: + if (continueSysex) { + // Continuation of a SysEx message + continueSysex = incomingMessage[numBytesReceived - 1] != 0xF7; + if (ignoreFlags & 0x01) continue; + } + // All other MIDI messages + } + + if (numMessagesReceived > 0 && numBytesReceived >= 0) { + auto message = self->inputData_.message; + + if (self->inputData_.firstMessage == true) { + message.timeStamp = 0.0; + self->inputData_.firstMessage = false; + } else { + message.timeStamp = (timestamp * 0.000001) - self->lastTime; + } + self->lastTime = (timestamp * 0.000001); + + if (!continueSysex) message.bytes.clear(); + + if ( !( ( continueSysex || incomingMessage[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) { + // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx, + // copy the event buffer into the MIDI message struct. + for (unsigned int i=0; iinputData_.usingCallback) { + auto callback = (RtMidiIn::RtMidiCallback) self->inputData_.userCallback; + callback(message.timeStamp, &message.bytes, self->inputData_.userData); + } else { + if (!self->inputData_.queue.push(message)) + std::cerr << "\nMidiInAndroid: message queue limit reached!!\n\n"; + } + } + } + } + + return NULL; +} + + +//*********************************************************************// +// API: Android AMIDI +// Class Definitions: MidiOutAndroid +//*********************************************************************// + + +MidiOutAndroid :: MidiOutAndroid( const std::string &clientName ) : MidiOutApi() { + MidiOutAndroid::initialize( clientName ); +} + +void MidiOutAndroid :: initialize( const std::string& clientName ) { + androidClientName = clientName; + connect(); +} + +void MidiOutAndroid :: connect() { + auto env = androidGetThreadEnv(); + auto context = androidGetContext(env); + androidRefreshMidiDevices(env, context, false); + + env->DeleteLocalRef(context); +} + +MidiOutAndroid :: ~MidiOutAndroid() { + auto env = androidGetThreadEnv(); + + // Remove all midi devices + for (jobject jMidiDevice : androidMidiDevices) { + env->DeleteGlobalRef(jMidiDevice); + } + androidMidiDevices.clear(); + + androidClientName = ""; +} + +void MidiOutAndroid :: openPort( unsigned int portNumber, const std::string &portName ) { + if (portNumber >= androidMidiDevices.size()) { + errorString_ = "MidiOutAndroid::openPort: Invalid port number"; + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + + return; + } + + androidOpenDevice(androidMidiDevices[portNumber], this, true); +} + +void MidiOutAndroid :: openVirtualPort( const std::string &portName ) { + errorString_ = "MidiOutAndroid::openVirtualPort: this function is not implemented for the Android API!"; + error( RtMidiError::WARNING, errorString_ ); +} + +unsigned int MidiOutAndroid :: getPortCount() { + connect(); + return androidMidiDevices.size(); +} + +std::string MidiOutAndroid :: getPortName( unsigned int portNumber ) { + auto env = androidGetThreadEnv(); + return androidPortName(env, portNumber); +} + +void MidiOutAndroid :: closePort() { + AMidiDevice_release(sendDevice); + sendDevice = NULL; + midiInputPort = NULL; +} + +void MidiOutAndroid:: setClientName( const std::string& name ) { + androidClientName = name; +} + +void MidiOutAndroid :: setPortName( const std::string &portName ) { + errorString_ = "MidiOutAndroid::setPortName: this function is not implemented for the Android API!"; + error( RtMidiError::WARNING, errorString_ ); +} + +void MidiOutAndroid :: sendMessage( const unsigned char *message, size_t size ) { + AMidiInputPort_send(midiInputPort, (uint8_t*)message, size); +} + +#endif // __AMIDI__ diff --git a/src/RtWvIn.cpp b/src/RtWvIn.cpp index 43c92ca..927c47a 100644 --- a/src/RtWvIn.cpp +++ b/src/RtWvIn.cpp @@ -70,24 +70,28 @@ void RtWvIn :: fillBuffer( void *buffer, unsigned int nFrames ) } } -RtWvIn :: RtWvIn( unsigned int nChannels, StkFloat sampleRate, int device, int bufferFrames, int nBuffers ) +RtWvIn :: RtWvIn( unsigned int nChannels, StkFloat sampleRate, int deviceIndex, int bufferFrames, int nBuffers ) : stopped_( true ), readIndex_( 0 ), writeIndex_( 0 ), framesFilled_( 0 ) { + std::vector deviceIds = adc_.getDeviceIds(); + if ( deviceIds.size() < 1 ) + handleError( "RtWvIn: No audio devices found!", StkError::AUDIO_SYSTEM ); + // We'll let RtAudio deal with channel and sample rate limitations. RtAudio::StreamParameters parameters; - if ( device == 0 ) + if ( deviceIndex == 0 ) parameters.deviceId = adc_.getDefaultInputDevice(); - else - parameters.deviceId = device - 1; + else { + if ( deviceIndex >= deviceIds.size() ) + handleError( "RtWvIn: Device index is invalid.", StkError::AUDIO_SYSTEM ); + parameters.deviceId = deviceIds[deviceIndex-1]; + } parameters.nChannels = nChannels; unsigned int size = bufferFrames; RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32; - try { - adc_.openStream( NULL, ¶meters, format, (unsigned int)Stk::sampleRate(), &size, &read, (void *)this ); - } - catch ( RtAudioError &error ) { - handleError( error.what(), StkError::AUDIO_SYSTEM ); + if ( adc_.openStream( NULL, ¶meters, format, (unsigned int)Stk::sampleRate(), &size, &read, (void *)this ) ) { + handleError( adc_.getErrorText(), StkError::AUDIO_SYSTEM ); } data_.resize( size * nBuffers, nChannels ); diff --git a/src/RtWvOut.cpp b/src/RtWvOut.cpp index e5e3cf3..cbc770b 100644 --- a/src/RtWvOut.cpp +++ b/src/RtWvOut.cpp @@ -90,25 +90,29 @@ int RtWvOut :: readBuffer( void *buffer, unsigned int frameCount ) } -RtWvOut :: RtWvOut( unsigned int nChannels, StkFloat sampleRate, int device, int bufferFrames, int nBuffers ) +RtWvOut :: RtWvOut( unsigned int nChannels, StkFloat sampleRate, int deviceIndex, int bufferFrames, int nBuffers ) : stopped_( true ), readIndex_( 0 ), writeIndex_( 0 ), framesFilled_( 0 ), status_(0) { + std::vector deviceIds = dac_.getDeviceIds(); + if ( deviceIds.size() < 1 ) + handleError( "RtWvOut: No audio devices found!", StkError::AUDIO_SYSTEM ); + // We'll let RtAudio deal with channel and sample rate limitations. RtAudio::StreamParameters parameters; - if ( device == 0 ) + if ( deviceIndex == 0 ) parameters.deviceId = dac_.getDefaultOutputDevice(); - else - parameters.deviceId = device - 1; + else { + if ( deviceIndex >= deviceIds.size() ) + handleError( "RtWvOut: Device index is invalid.", StkError::AUDIO_SYSTEM ); + parameters.deviceId = deviceIds[deviceIndex-1]; + } parameters.nChannels = nChannels; unsigned int size = bufferFrames; RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32; // Open a stream and set the callback function. - try { - dac_.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &size, &write, (void *)this ); - } - catch ( RtAudioError &error ) { - handleError( error.what(), StkError::AUDIO_SYSTEM ); + if ( dac_.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &size, &write, (void *)this ) ) { + handleError( dac_.getErrorText(), StkError::AUDIO_SYSTEM ); } data_.resize( size * nBuffers, nChannels ); diff --git a/src/Saxofony.cpp b/src/Saxofony.cpp index 0877a54..bb5a3ed 100644 --- a/src/Saxofony.cpp +++ b/src/Saxofony.cpp @@ -121,8 +121,8 @@ void Saxofony :: startBlowing( StkFloat amplitude, StkFloat rate ) void Saxofony :: stopBlowing( StkFloat rate ) { - if ( rate <= 0.0 ) { - oStream_ << "Saxofony::stopBlowing: argument is less than or equal to zero!"; + if ( rate < 0.0 ) { + oStream_ << "Saxofony::stopBlowing: argument is less than zero!"; handleError( StkError::WARNING ); return; }