diff --git a/configure.ac b/configure.ac index 80a8a0f..80c8c79 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ # Process this file with autoconf to produce a configure script. -AC_INIT(STK, 4.6.0, gary@music.mcgill.ca, stk) +AC_INIT(STK, 4.6.1, gary@music.mcgill.ca, stk) AC_CONFIG_AUX_DIR(config) AC_CONFIG_SRCDIR(src/Stk.cpp) AC_CONFIG_FILES(Makefile src/Makefile projects/demo/Makefile projects/effects/Makefile projects/ragamatic/Makefile projects/examples/Makefile projects/examples/libMakefile projects/eguitar/Makefile) diff --git a/doc/ReleaseNotes.txt b/doc/ReleaseNotes.txt index ffcbf96..c71de11 100644 --- a/doc/ReleaseNotes.txt +++ b/doc/ReleaseNotes.txt @@ -2,7 +2,7 @@ The Synthesis ToolKit in C++ (STK) By Perry R. Cook and Gary P. Scavone, 1995--2019. -v.4.6.1 (18 April 2017) +v.4.6.1 (18 April 2019) - see github site for complete details (github.com/thestk/stk) - various documentation updates - new Recorder (flute a la Verge) class (thanks to Mathias Bredholt) diff --git a/include/RtAudio.h b/include/RtAudio.h index ee28957..17f442b 100644 --- a/include/RtAudio.h +++ b/include/RtAudio.h @@ -7,6 +7,7 @@ and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. + RtAudio GitHub site: https://github.com/thestk/rtaudio RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes @@ -45,7 +46,21 @@ #ifndef __RTAUDIO_H #define __RTAUDIO_H -#define RTAUDIO_VERSION "5.0.0" +#define RTAUDIO_VERSION "5.1.0" + +#if defined _WIN32 || defined __CYGWIN__ + #if defined(RTAUDIO_EXPORT) + #define RTAUDIO_DLL_PUBLIC __declspec(dllexport) + #else + #define RTAUDIO_DLL_PUBLIC + #endif +#else + #if __GNUC__ >= 4 + #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) ) + #else + #define RTAUDIO_DLL_PUBLIC + #endif +#endif #include #include @@ -179,6 +194,7 @@ static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output \param userData A pointer to optional data provided by the client when opening the stream (default = NULL). + \return To continue normal stream operation, the RtAudioCallback function should return a value of zero. To stop the stream and drain the output buffer, the function should return a value of one. To abort @@ -200,7 +216,7 @@ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, */ /************************************************************************/ -class RtAudioError : public std::runtime_error +class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error { public: //! Defined RtAudioError types. @@ -260,7 +276,7 @@ typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string class RtApi; -class RtAudio +class RTAUDIO_DLL_PUBLIC RtAudio { public: @@ -271,11 +287,12 @@ class RtAudio 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 CoreAudio API. */ + MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_DS, /*!< The Microsoft DirectSound API. */ - RTAUDIO_DUMMY /*!< A compilable but non-functional API. */ + RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ + NUM_APIS /*!< Number of values in this enum. */ }; //! The public device information structure for returning queried values. @@ -387,6 +404,29 @@ class RtAudio */ static void getCompiledApi( std::vector &apis ); + //! Return the name of a specified compiled audio API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName( RtAudio::Api api ); + + //! Return the display name of a specified compiled audio API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName( RtAudio::Api api ); + + //! 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 + matches. On failure, the function returns UNSPECIFIED. + */ + static RtAudio::Api getCompiledApiByName( const std::string &name ); + //! The class constructor. /*! The constructor performs minor initialization tasks. An exception @@ -583,6 +623,7 @@ class RtAudio #endif #include #include + #include typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; @@ -651,7 +692,6 @@ class S24 { return *this; } - S24( const S24& v ) { *this = v; } S24( const double& d ) { *this = (int) d; } S24( const float& f ) { *this = (int) f; } S24( const signed short& s ) { *this = (int) s; } @@ -671,7 +711,7 @@ class S24 { #include -class RtApi +class RTAUDIO_DLL_PUBLIC RtApi { public: @@ -864,7 +904,6 @@ public: void startStream( void ); void stopStream( void ); void abortStream( void ); - long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, @@ -900,7 +939,6 @@ public: void startStream( void ); void stopStream( void ); void abortStream( void ); - long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, @@ -935,7 +973,6 @@ public: void startStream( void ); void stopStream( void ); void abortStream( void ); - long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, @@ -973,7 +1010,6 @@ public: void startStream( void ); void stopStream( void ); void abortStream( void ); - long getStreamLatency( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, @@ -1003,7 +1039,7 @@ class RtApiWasapi : public RtApi { public: RtApiWasapi(); - ~RtApiWasapi(); + virtual ~RtApiWasapi(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } unsigned int getDeviceCount( void ); diff --git a/include/RtMidi.h b/include/RtMidi.h index e55283e..9fc520e 100644 --- a/include/RtMidi.h +++ b/include/RtMidi.h @@ -5,7 +5,8 @@ This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. - RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ + RtMidi GitHub site: https://github.com/thestk/rtmidi + RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2019 Gary P. Scavone @@ -43,7 +44,21 @@ #ifndef RTMIDI_H #define RTMIDI_H -#define RTMIDI_VERSION "3.0.0" +#if defined _WIN32 || defined __CYGWIN__ + #if defined(RTMIDI_EXPORT) + #define RTMIDI_DLL_PUBLIC __declspec(dllexport) + #else + #define RTMIDI_DLL_PUBLIC + #endif +#else + #if __GNUC__ >= 4 + #define RTMIDI_DLL_PUBLIC __attribute__( (visibility( "default" )) ) + #else + #define RTMIDI_DLL_PUBLIC + #endif +#endif + +#define RTMIDI_VERSION "4.0.0" #include #include @@ -60,7 +75,7 @@ */ /************************************************************************/ -class RtMidiError : public std::exception +class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception { public: //! Defined RtMidiError types. @@ -79,8 +94,9 @@ class RtMidiError : public std::exception }; //! The constructor. - RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {} - + RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() + : message_(message), type_(type) {} + //! The destructor. virtual ~RtMidiError( void ) throw() {} @@ -88,10 +104,10 @@ class RtMidiError : public std::exception virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } //! Returns the thrown error message type. - virtual const Type& getType(void) const throw() { return type_; } + virtual const Type& getType( void ) const throw() { return type_; } //! Returns the thrown error message string. - virtual const std::string& getMessage(void) const throw() { return message_; } + virtual const std::string& getMessage( void ) const throw() { return message_; } //! Returns the thrown error message as a c-style string. virtual const char* what( void ) const throw() { return message_.c_str(); } @@ -113,10 +129,9 @@ typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string & class MidiApi; -class RtMidi +class RTMIDI_DLL_PUBLIC RtMidi { public: - //! MIDI API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ @@ -124,7 +139,8 @@ class RtMidi LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ - RTMIDI_DUMMY /*!< A compilable but non-functional API. */ + RTMIDI_DUMMY, /*!< A compilable but non-functional API. */ + NUM_APIS /*!< Number of values in this enum. */ }; //! A static function to determine the current RtMidi version. @@ -138,6 +154,29 @@ class RtMidi */ static void getCompiledApi( std::vector &apis ) throw(); + //! Return the name of a specified compiled MIDI API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName( RtMidi::Api api ); + + //! Return the display name of a specified compiled MIDI API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName( RtMidi::Api api ); + + //! Return the compiled MIDI API having the given name. + /*! + A case insensitive comparison will check the specified name + against the list of compiled APIs, and return the one which + matches. On failure, the function returns UNSPECIFIED. + */ + static RtMidi::Api getCompiledApiByName( const std::string &name ); + //! Pure virtual openPort() function. virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0; @@ -153,6 +192,9 @@ class RtMidi //! Pure virtual closePort() function. virtual void closePort( void ) = 0; + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + //! Returns true if a port is open and false if not. /*! Note that this only applies to connections made with the openPort() @@ -168,10 +210,8 @@ class RtMidi virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; protected: - RtMidi(); virtual ~RtMidi(); - MidiApi *rtapi_; }; @@ -188,8 +228,6 @@ class RtMidi time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also possible to open a virtual input port to which other MIDI software clients can connect. - - by Gary P. Scavone, 2003-2019. */ /**********************************************************************/ @@ -207,12 +245,12 @@ class RtMidi // // **************************************************************** // -class RtMidiIn : public RtMidi +class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi { public: //! User callback function type definition. - typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData); + typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData ); //! Default constructor that allows an optional api, client name and queue size. /*! @@ -335,7 +373,6 @@ class RtMidiIn : public RtMidi protected: void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ); - }; /**********************************************************************/ @@ -349,15 +386,12 @@ class RtMidiIn : public RtMidi connect to more than one MIDI device at the same time. With the OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a virtual port to which other MIDI software clients can connect. - - by Gary P. Scavone, 2003-2019. */ /**********************************************************************/ -class RtMidiOut : public RtMidi +class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi { public: - //! Default constructor that allows an optional client name. /*! An exception will be thrown if a MIDI system initialization error occurs. @@ -458,7 +492,7 @@ class RtMidiOut : public RtMidi // // **************************************************************** // -class MidiApi +class RTMIDI_DLL_PUBLIC MidiApi { public: @@ -468,6 +502,8 @@ class MidiApi virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0; virtual void openVirtualPort( const std::string &portName ) = 0; virtual void closePort( void ) = 0; + virtual void setClientName( const std::string &clientName ) = 0; + virtual void setPortName( const std::string &portName ) = 0; virtual unsigned int getPortCount( void ) = 0; virtual std::string getPortName( unsigned int portNumber ) = 0; @@ -489,7 +525,7 @@ protected: void *errorCallbackUserData_; }; -class MidiInApi : public MidiApi +class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi { public: @@ -502,15 +538,15 @@ class MidiInApi : public MidiApi // A MIDI structure used internally by the class to store incoming // messages. Each message represents one and only one MIDI message. - struct MidiMessage { - std::vector bytes; + struct MidiMessage { + std::vector bytes; //! Time in seconds elapsed since the previous message double timeStamp; // Default constructor. - MidiMessage() - :bytes(0), timeStamp(0.0) {} + MidiMessage() + : bytes(0), timeStamp(0.0) {} }; struct MidiQueue { @@ -520,12 +556,11 @@ class MidiInApi : public MidiApi MidiMessage *ring; // Default constructor. - MidiQueue() - :front(0), back(0), ringSize(0), ring(0) {} - bool push(const MidiMessage&); - bool pop(std::vector*, double*); - unsigned int size(unsigned int *back=0, - unsigned int *front=0); + MidiQueue() + : front(0), back(0), ringSize(0), ring(0) {} + bool push( const MidiMessage& ); + bool pop( std::vector*, double* ); + unsigned int size( unsigned int *back=0, unsigned int *front=0 ); }; // The RtMidiInData structure is used to pass private class data to @@ -543,17 +578,16 @@ class MidiInApi : public MidiApi bool continueSysex; // Default constructor. - RtMidiInData() - : ignoreFlags(7), doInput(false), firstMessage(true), - apiData(0), usingCallback(false), userCallback(0), userData(0), - continueSysex(false) {} + RtMidiInData() + : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false), + userCallback(0), userData(0), continueSysex(false) {} }; protected: RtMidiInData inputData_; }; -class MidiOutApi : public MidiApi +class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi { public: @@ -573,12 +607,12 @@ inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string &po inline void RtMidiIn :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } -inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); } -inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); } +inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { static_cast(rtapi_)->setCallback( callback, userData ); } +inline void RtMidiIn :: cancelCallback( void ) { static_cast(rtapi_)->cancelCallback(); } inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } -inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } -inline double RtMidiIn :: getMessage( std::vector *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); } +inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { static_cast(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } +inline double RtMidiIn :: getMessage( std::vector *message ) { return static_cast(rtapi_)->getMessage( message ); } inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } @@ -588,207 +622,8 @@ inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } -inline void RtMidiOut :: sendMessage( const std::vector *message ) { ((MidiOutApi *)rtapi_)->sendMessage( &message->at(0), message->size() ); } -inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { ((MidiOutApi *)rtapi_)->sendMessage( message, size ); } +inline void RtMidiOut :: sendMessage( const std::vector *message ) { static_cast(rtapi_)->sendMessage( &message->at(0), message->size() ); } +inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { static_cast(rtapi_)->sendMessage( message, size ); } inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } -// **************************************************************** // -// -// MidiInApi and MidiOutApi subclass prototypes. -// -// **************************************************************** // - -#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) - #define __RTMIDI_DUMMY__ -#endif - -#if defined(__MACOSX_CORE__) - -class MidiInCore: public MidiInApi -{ - public: - MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ); - ~MidiInCore( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - - protected: - void initialize( const std::string& clientName ); -}; - -class MidiOutCore: public MidiOutApi -{ - public: - MidiOutCore( const std::string &clientName ); - ~MidiOutCore( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - void sendMessage( const unsigned char *message, size_t size ); - - protected: - void initialize( const std::string& clientName ); -}; - -#endif - -#if defined(__UNIX_JACK__) - -class MidiInJack: public MidiInApi -{ - public: - MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ); - ~MidiInJack( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - - protected: - std::string clientName; - - void connect( void ); - void initialize( const std::string& clientName ); -}; - -class MidiOutJack: public MidiOutApi -{ - public: - MidiOutJack( const std::string &clientName ); - ~MidiOutJack( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - void sendMessage( const unsigned char *message, size_t size ); - - protected: - std::string clientName; - - void connect( void ); - void initialize( const std::string& clientName ); -}; - -#endif - -#if defined(__LINUX_ALSA__) - -class MidiInAlsa: public MidiInApi -{ - public: - MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ); - ~MidiInAlsa( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - - protected: - void initialize( const std::string& clientName ); -}; - -class MidiOutAlsa: public MidiOutApi -{ - public: - MidiOutAlsa( const std::string &clientName ); - ~MidiOutAlsa( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - void sendMessage( const unsigned char *message, size_t size ); - - protected: - void initialize( const std::string& clientName ); -}; - -#endif - -#if defined(__WINDOWS_MM__) - -class MidiInWinMM: public MidiInApi -{ - public: - MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ); - ~MidiInWinMM( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - - protected: - void initialize( const std::string& clientName ); -}; - -class MidiOutWinMM: public MidiOutApi -{ - public: - MidiOutWinMM( const std::string &clientName ); - ~MidiOutWinMM( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; - void openPort( unsigned int portNumber, const std::string &portName ); - void openVirtualPort( const std::string &portName ); - void closePort( void ); - unsigned int getPortCount( void ); - std::string getPortName( unsigned int portNumber ); - void sendMessage( const unsigned char *message, size_t size ); - - protected: - void initialize( const std::string& clientName ); -}; - -#endif - -#if defined(__RTMIDI_DUMMY__) - -class MidiInDummy: public MidiInApi -{ - public: - MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } - RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } - void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} - void openVirtualPort( const std::string &/*portName*/ ) {} - void closePort( void ) {} - unsigned int getPortCount( void ) { return 0; } - std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } - - protected: - void initialize( const std::string& /*clientName*/ ) {} -}; - -class MidiOutDummy: public MidiOutApi -{ - public: - MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } - RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } - void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} - void openVirtualPort( const std::string &/*portName*/ ) {} - void closePort( void ) {} - unsigned int getPortCount( void ) { return 0; } - std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } - void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {} - - protected: - void initialize( const std::string& /*clientName*/ ) {} -}; - -#endif - #endif diff --git a/projects/demo/tcl/Demo.tcl b/projects/demo/tcl/Demo.tcl index 5ac7950..b8bb8a0 100644 --- a/projects/demo/tcl/Demo.tcl +++ b/projects/demo/tcl/Demo.tcl @@ -1,5 +1,5 @@ # Tcl/Tk Demo GUI for the Synthesis Toolkit (STK) -# by Gary P. Scavone, CCRMA, Stanford University, 1995--2017. +# by Gary P. Scavone, CCRMA, CAML, Stanford & McGill Universities, 1995--2019. # Set initial control values set pitch 64.0 diff --git a/projects/effects/README-effects.txt b/projects/effects/README-effects.txt index 6606074..e3d2b2a 100644 --- a/projects/effects/README-effects.txt +++ b/projects/effects/README-effects.txt @@ -1,6 +1,6 @@ The Synthesis ToolKit in C++ (STK) -By Perry R. Cook and Gary P. Scavone, 1995--2017. +By Perry R. Cook and Gary P. Scavone, 1995--2019. EFFECTS PROJECT: diff --git a/projects/ragamatic/Drone.cpp b/projects/ragamatic/Drone.cpp index 3e173b6..ed2ca45 100644 --- a/projects/ragamatic/Drone.cpp +++ b/projects/ragamatic/Drone.cpp @@ -13,7 +13,7 @@ Stanford, bearing the names of Karplus and/or Strong. - by Perry R. Cook and Gary P. Scavone, 1995--2017. + by Perry R. Cook and Gary P. Scavone, 1995--2019. */ /***************************************************/ diff --git a/projects/ragamatic/Drone.h b/projects/ragamatic/Drone.h index 0c8331d..0892cd5 100644 --- a/projects/ragamatic/Drone.h +++ b/projects/ragamatic/Drone.h @@ -24,7 +24,7 @@ namespace stk { Stanford, bearing the names of Karplus and/or Strong. - by Perry R. Cook and Gary P. Scavone, 1995--2017. + by Perry R. Cook and Gary P. Scavone, 1995--2019. */ /***************************************************/ diff --git a/projects/ragamatic/Tabla.cpp b/projects/ragamatic/Tabla.cpp index 251a7ba..8d0bc76 100644 --- a/projects/ragamatic/Tabla.cpp +++ b/projects/ragamatic/Tabla.cpp @@ -8,7 +8,7 @@ sample rates. You can specify the maximum polyphony (maximum number of simultaneous voices) in Tabla.h. - by Perry R. Cook and Gary P. Scavone, 1995--2017. + by Perry R. Cook and Gary P. Scavone, 1995--2019. */ /***************************************************/ diff --git a/projects/ragamatic/Tabla.h b/projects/ragamatic/Tabla.h index 2215fa0..9d9f21b 100644 --- a/projects/ragamatic/Tabla.h +++ b/projects/ragamatic/Tabla.h @@ -17,7 +17,7 @@ namespace stk { sample rates. You can specify the maximum polyphony (maximum number of simultaneous voices) in Tabla.h. - by Perry R. Cook and Gary P. Scavone, 1995--2017. + by Perry R. Cook and Gary P. Scavone, 1995--2019. */ /***************************************************/ diff --git a/projects/ragamatic/VoicDrum.cpp b/projects/ragamatic/VoicDrum.cpp index 0597ce4..a149f51 100644 --- a/projects/ragamatic/VoicDrum.cpp +++ b/projects/ragamatic/VoicDrum.cpp @@ -8,7 +8,7 @@ sample rates. You can specify the maximum polyphony (maximum number of simultaneous voices) in VoicDrum.h. - by Perry R. Cook and Gary P. Scavone, 1995--2017. + by Perry R. Cook and Gary P. Scavone, 1995--2019. */ /***************************************************/ diff --git a/projects/ragamatic/VoicDrum.h b/projects/ragamatic/VoicDrum.h index 2e50e01..9bd84f0 100644 --- a/projects/ragamatic/VoicDrum.h +++ b/projects/ragamatic/VoicDrum.h @@ -17,7 +17,7 @@ namespace stk { sample rates. You can specify the maximum polyphony (maximum number of simultaneous voices) in VoicDrum.h. - by Perry R. Cook and Gary P. Scavone, 1995--2017. + by Perry R. Cook and Gary P. Scavone, 1995--2019. */ /***************************************************/ diff --git a/src/RtAudio.cpp b/src/RtAudio.cpp index a320a32..0837d98 100644 --- a/src/RtAudio.cpp +++ b/src/RtAudio.cpp @@ -1,4 +1,4 @@ -/************************************************************************/ +/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. @@ -7,6 +7,7 @@ and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. + RtAudio GitHub site: https://github.com/thestk/rtaudio RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes @@ -38,7 +39,7 @@ */ /************************************************************************/ -// RtAudio: Version 5.0.0 +// RtAudio: Version 5.1.0 #include "RtAudio.h" #include @@ -98,39 +99,95 @@ std::string RtAudio :: getVersion( void ) return RTAUDIO_VERSION; } -void RtAudio :: getCompiledApi( std::vector &apis ) -{ - apis.clear(); +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char* rtaudio_api_names[][2] = { + { "unspecified" , "Unknown" }, + { "alsa" , "ALSA" }, + { "pulse" , "Pulse" }, + { "oss" , "OpenSoundSystem" }, + { "jack" , "Jack" }, + { "core" , "CoreAudio" }, + { "wasapi" , "WASAPI" }, + { "asio" , "ASIO" }, + { "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. +// 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(__UNIX_JACK__) - apis.push_back( UNIX_JACK ); -#endif -#if defined(__LINUX_ALSA__) - apis.push_back( LINUX_ALSA ); + RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) - apis.push_back( LINUX_PULSE ); + RtAudio::LINUX_PULSE, +#endif +#if defined(__LINUX_ALSA__) + RtAudio::LINUX_ALSA, #endif #if defined(__LINUX_OSS__) - apis.push_back( LINUX_OSS ); + RtAudio::LINUX_OSS, #endif #if defined(__WINDOWS_ASIO__) - apis.push_back( WINDOWS_ASIO ); + RtAudio::WINDOWS_ASIO, #endif #if defined(__WINDOWS_WASAPI__) - apis.push_back( WINDOWS_WASAPI ); + RtAudio::WINDOWS_WASAPI, #endif #if defined(__WINDOWS_DS__) - apis.push_back( WINDOWS_DS ); + RtAudio::WINDOWS_DS, #endif #if defined(__MACOSX_CORE__) - apis.push_back( MACOSX_CORE ); + RtAudio::MACOSX_CORE, #endif #if defined(__RTAUDIO_DUMMY__) - apis.push_back( 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; +} + +// This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. +// If the build breaks here, check that they match. +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +class StaticAssertions { StaticAssertions() { + StaticAssert(); +}}; + +void RtAudio :: getCompiledApi( std::vector &apis ) +{ + apis = std::vector(rtaudio_compiled_apis, + rtaudio_compiled_apis + rtaudio_num_compiled_apis); +} + +std::string RtAudio :: getApiName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return ""; + return rtaudio_api_names[api][0]; +} + +std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return "Unknown"; + return rtaudio_api_names[api][1]; +} + +RtAudio::Api RtAudio :: getCompiledApiByName( 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]][0]) + return rtaudio_compiled_apis[i]; + return RtAudio::UNSPECIFIED; } void RtAudio :: openRtApi( RtAudio::Api api ) @@ -1485,6 +1542,10 @@ void RtApiCore :: startStream( void ) return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { @@ -1845,7 +1906,10 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, unlock: //MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); + // 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(); + return SUCCESS; } @@ -1944,7 +2008,9 @@ struct JackHandle { :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; +#if !defined(__RTAUDIO_DEBUG__) static void jackSilentError( const char * ) {}; +#endif RtApiJack :: RtApiJack() :shouldAutoconnect_(true) { @@ -1971,7 +2037,7 @@ unsigned int RtApiJack :: getDeviceCount( void ) const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 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; @@ -2010,7 +2076,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) const char **ports; std::string port, previousPort; unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 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; @@ -2045,7 +2111,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(), NULL, JackPortIsInput ); + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2054,7 +2120,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(), NULL, JackPortIsOutput ); + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2128,7 +2194,7 @@ static void jackShutdown( void *infoPointer ) static int jackXrun( void *infoPointer ) { - JackHandle *handle = (JackHandle *) infoPointer; + JackHandle *handle = *((JackHandle **) infoPointer); if ( handle->ports[0] ) handle->xrun[0] = true; if ( handle->ports[1] ) handle->xrun[1] = true; @@ -2166,7 +2232,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne const char **ports; std::string port, previousPort, deviceName; unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 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; @@ -2190,22 +2256,24 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne return FAILURE; } - // Count the available ports containing the client name as device - // channels. Jack "input ports" equal RtAudio output channels. - unsigned int nChannels = 0; unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; - ports = jack_get_ports( client, deviceName.c_str(), NULL, 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 << ")."; - errorText_ = errorStream_.str(); - return FAILURE; + 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 ); + 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 << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } } // Check the jack server sample rate. @@ -2219,7 +2287,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(), NULL, flag ); + ports = jack_get_ports( client, 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); @@ -2332,7 +2400,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne else { stream_.mode = mode; jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); - jack_set_xrun_callback( handle->client, jackXrun, (void *) &handle ); + jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); } @@ -2439,6 +2507,10 @@ void RtApiJack :: startStream( void ) return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { @@ -2451,7 +2523,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(), NULL, JackPortIsInput); + ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; @@ -2475,7 +2547,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(), NULL, JackPortIsOutput ); + ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; @@ -3318,6 +3390,10 @@ void RtApiAsio :: startStream() return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { @@ -3685,11 +3761,33 @@ static const char* getAsioErrorString( ASIOError result ) #ifndef INITGUID #define INITGUID #endif + +#include +#include +#include +#include +#include + #include #include #include #include +#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT + #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) +#endif + +#ifndef MFSTARTUP_NOSOCKET + #define MFSTARTUP_NOSOCKET 0x1 +#endif + +#ifdef _MSC_VER + #pragma comment( lib, "ksuser" ) + #pragma comment( lib, "mfplat.lib" ) + #pragma comment( lib, "mfuuid.lib" ) + #pragma comment( lib, "wmcodecdspuuid" ) +#endif + //============================================================================= #define SAFE_RELEASE( objectPtr )\ @@ -3747,8 +3845,9 @@ public: relOutIndex += bufferSize_; } - // "in" index can end on the "out" index but cannot begin at it - if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { + // the "IN" index CAN BEGIN at the "OUT" index + // the "IN" index CANNOT END at the "OUT" index + if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { return false; // not enough space between "in" index and "out" index } @@ -3808,8 +3907,9 @@ public: relInIndex += bufferSize_; } - // "out" index can begin at and end on the "in" index - if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { + // the "OUT" index CANNOT BEGIN at the "IN" index + // the "OUT" index CAN END at the "IN" index + if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } @@ -3863,145 +3963,194 @@ private: //----------------------------------------------------------------------------- // In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate -// between HW and the user. The convertBufferWasapi function is used to perform this conversion -// between HwIn->UserIn and UserOut->HwOut during the stream callback loop. -// This sample rate converter works best with conversions between one rate and its multiple. -void convertBufferWasapi( char* outBuffer, - const char* inBuffer, - const unsigned int& channelCount, - const unsigned int& inSampleRate, - const unsigned int& outSampleRate, - const unsigned int& inSampleCount, - unsigned int& outSampleCount, - const RtAudioFormat& format ) +// between HW and the user. The WasapiResampler class is used to perform this conversion between +// HwIn->UserIn and UserOut->HwOut during the stream callback loop. +class WasapiResampler { - // calculate the new outSampleCount and relative sampleStep - float sampleRatio = ( float ) outSampleRate / inSampleRate; - float sampleRatioInv = ( float ) 1 / sampleRatio; - float sampleStep = 1.0f / sampleRatio; - float inSampleFraction = 0.0f; +public: + WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, + unsigned int inSampleRate, unsigned int outSampleRate ) + : _bytesPerSample( bitsPerSample / 8 ) + , _channelCount( channelCount ) + , _sampleRatio( ( float ) outSampleRate / inSampleRate ) + , _transformUnk( NULL ) + , _transform( NULL ) + , _mediaType( NULL ) + , _inputMediaType( NULL ) + , _outputMediaType( NULL ) - outSampleCount = ( unsigned int ) std::roundf( inSampleCount * sampleRatio ); - - // if inSampleRate is a multiple of outSampleRate (or vice versa) there's no need to interpolate - if ( floor( sampleRatio ) == sampleRatio || floor( sampleRatioInv ) == sampleRatioInv ) + #ifdef __IWMResamplerProps_FWD_DEFINED__ + , _resamplerProps( NULL ) + #endif { - // frame-by-frame, copy each relative input sample into it's corresponding output sample - for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) - { - unsigned int inSample = ( unsigned int ) inSampleFraction; + // 1. Initialization - switch ( format ) - { - case RTAUDIO_SINT8: - memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) ); - break; - case RTAUDIO_SINT16: - memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) ); - break; - case RTAUDIO_SINT24: - memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) ); - break; - case RTAUDIO_SINT32: - memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) ); - break; - case RTAUDIO_FLOAT32: - memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) ); - break; - case RTAUDIO_FLOAT64: - memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) ); - break; - } + MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); - // jump to next in sample - inSampleFraction += sampleStep; - } + // 2. Create Resampler Transform Object + + CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, + IID_IUnknown, ( void** ) &_transformUnk ); + + _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); + _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality + #endif + + // 3. Specify input / output format + + MFCreateMediaType( &_mediaType ); + _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); + _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); + _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); + _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); + + MFCreateMediaType( &_inputMediaType ); + _mediaType->CopyAllItems( _inputMediaType ); + + _transform->SetInputType( 0, _inputMediaType, 0 ); + + MFCreateMediaType( &_outputMediaType ); + _mediaType->CopyAllItems( _outputMediaType ); + + _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); + _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); + + _transform->SetOutputType( 0, _outputMediaType, 0 ); + + // 4. Send stream start messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); } - else // else interpolate + + ~WasapiResampler() { - // frame-by-frame, copy each relative input sample into it's corresponding output sample - for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) - { - unsigned int inSample = ( unsigned int ) inSampleFraction; - float inSampleDec = inSampleFraction - inSample; - unsigned int frameInSample = inSample * channelCount; - unsigned int frameOutSample = outSample * channelCount; + // 8. Send stream stop messages to Resampler - switch ( format ) - { - case RTAUDIO_SINT8: - { - for ( unsigned int channel = 0; channel < channelCount; channel++ ) - { - char fromSample = ( ( char* ) inBuffer )[ frameInSample + channel ]; - char toSample = ( ( char* ) inBuffer )[ frameInSample + channelCount + channel ]; - char sampleDiff = ( char ) ( ( toSample - fromSample ) * inSampleDec ); - ( ( char* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; - } - break; - } - case RTAUDIO_SINT16: - { - for ( unsigned int channel = 0; channel < channelCount; channel++ ) - { - short fromSample = ( ( short* ) inBuffer )[ frameInSample + channel ]; - short toSample = ( ( short* ) inBuffer )[ frameInSample + channelCount + channel ]; - short sampleDiff = ( short ) ( ( toSample - fromSample ) * inSampleDec ); - ( ( short* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; - } - break; - } - case RTAUDIO_SINT24: - { - for ( unsigned int channel = 0; channel < channelCount; channel++ ) - { - int fromSample = ( ( S24* ) inBuffer )[ frameInSample + channel ].asInt(); - int toSample = ( ( S24* ) inBuffer )[ frameInSample + channelCount + channel ].asInt(); - int sampleDiff = ( int ) ( ( toSample - fromSample ) * inSampleDec ); - ( ( S24* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; - } - break; - } - case RTAUDIO_SINT32: - { - for ( unsigned int channel = 0; channel < channelCount; channel++ ) - { - int fromSample = ( ( int* ) inBuffer )[ frameInSample + channel ]; - int toSample = ( ( int* ) inBuffer )[ frameInSample + channelCount + channel ]; - int sampleDiff = ( int ) ( ( toSample - fromSample ) * inSampleDec ); - ( ( int* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; - } - break; - } - case RTAUDIO_FLOAT32: - { - for ( unsigned int channel = 0; channel < channelCount; channel++ ) - { - float fromSample = ( ( float* ) inBuffer )[ frameInSample + channel ]; - float toSample = ( ( float* ) inBuffer )[ frameInSample + channelCount + channel ]; - float sampleDiff = ( toSample - fromSample ) * inSampleDec; - ( ( float* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; - } - break; - } - case RTAUDIO_FLOAT64: - { - for ( unsigned int channel = 0; channel < channelCount; channel++ ) - { - double fromSample = ( ( double* ) inBuffer )[ frameInSample + channel ]; - double toSample = ( ( double* ) inBuffer )[ frameInSample + channelCount + channel ]; - double sampleDiff = ( toSample - fromSample ) * inSampleDec; - ( ( double* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; - } - break; - } - } + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); - // jump to next in sample - inSampleFraction += sampleStep; - } + // 9. Cleanup + + MFShutdown(); + + SAFE_RELEASE( _transformUnk ); + SAFE_RELEASE( _transform ); + SAFE_RELEASE( _mediaType ); + SAFE_RELEASE( _inputMediaType ); + SAFE_RELEASE( _outputMediaType ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + SAFE_RELEASE( _resamplerProps ); + #endif } -} + + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) + { + unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; + if ( _sampleRatio == 1 ) + { + // no sample rate conversion required + memcpy( outBuffer, inBuffer, inputBufferSize ); + outSampleCount = inSampleCount; + return; + } + + unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + + IMFMediaBuffer* rInBuffer; + IMFSample* rInSample; + BYTE* rInByteBuffer = NULL; + + // 5. Create Sample object from input data + + MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); + + rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); + memcpy( rInByteBuffer, inBuffer, inputBufferSize ); + rInBuffer->Unlock(); + rInByteBuffer = NULL; + + rInBuffer->SetCurrentLength( inputBufferSize ); + + MFCreateSample( &rInSample ); + rInSample->AddBuffer( rInBuffer ); + + // 6. Pass input data to Resampler + + _transform->ProcessInput( 0, rInSample, 0 ); + + SAFE_RELEASE( rInBuffer ); + SAFE_RELEASE( rInSample ); + + // 7. Perform sample rate conversion + + IMFMediaBuffer* rOutBuffer = NULL; + BYTE* rOutByteBuffer = NULL; + + MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; + DWORD rStatus; + DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput + + // 7.1 Create Sample object for output data + + memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); + MFCreateSample( &( rOutDataBuffer.pSample ) ); + MFCreateMemoryBuffer( rBytes, &rOutBuffer ); + rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); + rOutDataBuffer.dwStreamID = 0; + rOutDataBuffer.dwStatus = 0; + rOutDataBuffer.pEvents = NULL; + + // 7.2 Get output data from Resampler + + if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) + { + outSampleCount = 0; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + return; + } + + // 7.3 Write output data to outBuffer + + SAFE_RELEASE( rOutBuffer ); + rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); + rOutBuffer->GetCurrentLength( &rBytes ); + + rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); + memcpy( outBuffer, rOutByteBuffer, rBytes ); + rOutBuffer->Unlock(); + rOutByteBuffer = NULL; + + outSampleCount = rBytes / _bytesPerSample / _channelCount; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + } + +private: + unsigned int _bytesPerSample; + unsigned int _channelCount; + float _sampleRatio; + + IUnknown* _transformUnk; + IMFTransform* _transform; + IMFMediaType* _mediaType; + IMFMediaType* _inputMediaType; + IMFMediaType* _outputMediaType; + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + IWMResamplerProps* _resamplerProps; + #endif +}; //----------------------------------------------------------------------------- @@ -4039,10 +4188,9 @@ RtApiWasapi::RtApiWasapi() CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; - error( RtAudioError::DRIVER_ERROR ); - } + // If this runs on an old Windows, it will fail. Ignore and proceed. + if ( FAILED( hr ) ) + deviceEnumerator_ = NULL; } //----------------------------------------------------------------------------- @@ -4069,6 +4217,9 @@ unsigned int RtApiWasapi::getDeviceCount( void ) IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; + if ( !deviceEnumerator_ ) + return 0; + // Count capture devices errorText_.clear(); HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); @@ -4418,6 +4569,10 @@ void RtApiWasapi::startStream( void ) return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + // update stream state stream_.state = STREAM_RUNNING; @@ -4457,26 +4612,6 @@ void RtApiWasapi::stopStream( void ) // Wait for the last buffer to play before stopping. Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); - // stop capture client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - - // stop render client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; @@ -4507,26 +4642,6 @@ void RtApiWasapi::abortStream( void ) Sleep( 1 ); } - // stop capture client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - - // stop render client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; @@ -4594,7 +4709,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne goto Exit; } - // determine whether index falls within capture or render devices + // if device index falls within capture devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { errorType = RtAudioError::INVALID_USE; @@ -4614,28 +4729,66 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } - else { - if ( mode != OUTPUT ) { - errorType = RtAudioError::INVALID_USE; - errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; + + // 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 + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + if ( !renderAudioClient ) + { + probeDeviceOpen( device, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); + } + + // retrieve captureAudioClient from devicePtr + 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; } - // retrieve renderAudioClient from devicePtr + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; + goto Exit; + } + + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + 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 IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + if ( renderAudioClient ) + { + methodResult = SUCCESS; + goto Exit; + } hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { @@ -4646,13 +4799,13 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = renderAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } @@ -4688,7 +4841,8 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || - stream_.nUserChannels != stream_.nDeviceChannels ) + stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || + stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) @@ -4781,6 +4935,8 @@ void RtApiWasapi::wasapiThread() float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; + WasapiResampler* captureResampler = NULL; + WasapiResampler* renderResampler = NULL; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; @@ -4789,7 +4945,8 @@ void RtApiWasapi::wasapiThread() unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; - bool callbackPushed = false; + bool loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT]; + bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; @@ -4799,14 +4956,15 @@ void RtApiWasapi::wasapiThread() unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; - errorText_.clear(); + std::string errorText; RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; - TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = + ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } @@ -4815,112 +4973,119 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } - captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + // init captureResampler + captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], + captureFormat->nSamplesPerSec, stream_.sampleRate ); - // initialize capture stream according to desire buffer size - float desiredBufferSize = stream_.bufferSize * captureSrRatio; - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); if ( !captureClient ) { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - desiredBufferPeriod, - desiredBufferPeriod, + loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, captureFormat, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } - // configure captureEvent to trigger on every available capture buffer - captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); - if ( !captureEvent ) { - errorType = RtAudioError::SYSTEM_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event."; - goto Exit; - } + // don't configure captureEvent if in loopback mode + if ( !loopbackEnabled ) + { + // configure captureEvent to trigger on every available capture buffer + captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !captureEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; + goto Exit; + } - hr = captureAudioClient->SetEventHandle( captureEvent ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; - goto Exit; + hr = captureAudioClient->SetEventHandle( captureEvent ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; } ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; - ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; + + // reset the capture stream + hr = captureAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; + goto Exit; + } + + // start the capture stream + hr = captureAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; + goto Exit; + } } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } // scale outBufferSize according to stream->user sample rate ratio - unsigned int outBufferSize = ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; + unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); - - // reset the capture stream - hr = captureAudioClient->Reset(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; - goto Exit; - } - - // start the capture stream - hr = captureAudioClient->Start(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream."; - goto Exit; - } } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } - renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + // init renderResampler + renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, renderFormat->nSamplesPerSec ); - // initialize render stream according to desire buffer size - float desiredBufferSize = stream_.bufferSize * renderSrRatio; - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); if ( !renderClient ) { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - desiredBufferPeriod, - desiredBufferPeriod, + 0, + 0, renderFormat, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } @@ -4928,69 +5093,75 @@ void RtApiWasapi::wasapiThread() renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RtAudioError::SYSTEM_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event."; + errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + + // reset the render stream + hr = renderAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; + goto Exit; + } + + // start the render stream + hr = renderAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; + goto Exit; + } } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } // scale inBufferSize according to user->stream sample rate ratio - unsigned int inBufferSize = ( unsigned int ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; + unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); - - // reset the render stream - hr = renderAudioClient->Reset(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream."; - goto Exit; - } - - // start the render stream - hr = renderAudioClient->Start(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream."; - goto Exit; - } } - if ( stream_.mode == INPUT ) { - convBuffSize = ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + // malloc buffer memory + if ( stream_.mode == INPUT ) + { + using namespace std; // for ceilf + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } - else if ( stream_.mode == OUTPUT ) { - convBuffSize = ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + else if ( stream_.mode == OUTPUT ) + { + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } - else if ( stream_.mode == DUPLEX ) { - convBuffSize = std::max( ( size_t ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), - ( size_t ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + else if ( stream_.mode == DUPLEX ) + { + convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } - convBuffer = ( char* ) malloc( convBuffSize ); - stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); + convBuffSize *= 2; // allow overflow for *SrRatio remainders + convBuffer = ( char* ) calloc( convBuffSize, 1 ); + stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; + errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } @@ -5003,23 +5174,43 @@ void RtApiWasapi::wasapiThread() // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count // Convert callback buffer to user format - if ( captureAudioClient ) { - // Pull callback buffer from inputBuffer - callbackPulled = captureBuffer.pullBuffer( convBuffer, - ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], - stream_.deviceFormat[INPUT] ); + if ( captureAudioClient ) + { + int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); + if ( captureSrRatio != 1 ) + { + // account for remainders + samplesToPull--; + } + + convBufferSize = 0; + while ( convBufferSize < stream_.bufferSize ) + { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + samplesToPull * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( !callbackPulled ) + { + break; + } - if ( callbackPulled ) { // Convert callback buffer to user sample rate - convertBufferWasapi( stream_.deviceBuffer, - convBuffer, - stream_.nDeviceChannels[INPUT], - captureFormat->nSamplesPerSec, - stream_.sampleRate, - ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), - convBufferSize, - stream_.deviceFormat[INPUT] ); + unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + unsigned int convSamples = 0; + captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, + convBuffer, + samplesToPull, + convSamples ); + + convBufferSize += convSamples; + samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples + } + + if ( callbackPulled ) + { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], @@ -5054,18 +5245,21 @@ void RtApiWasapi::wasapiThread() captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); + // tick stream time + RtApi::tickStreamTime(); + // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } @@ -5076,12 +5270,12 @@ void RtApiWasapi::wasapiThread() HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } @@ -5096,25 +5290,33 @@ void RtApiWasapi::wasapiThread() // 2. Convert callback buffer to stream sample rate and channel count // 3. Push callback buffer into outputBuffer - if ( renderAudioClient && callbackPulled ) { - if ( stream_.doConvertBuffer[OUTPUT] ) { - // Convert callback buffer to stream format - convertBuffer( stream_.deviceBuffer, - stream_.userBuffer[OUTPUT], - stream_.convertInfo[OUTPUT] ); + if ( renderAudioClient && callbackPulled ) + { + // if the last call to renderBuffer.PushBuffer() was successful + if ( callbackPushed || convBufferSize == 0 ) + { + if ( stream_.doConvertBuffer[OUTPUT] ) + { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + } + else { + // no further conversion, simple copy userBuffer to deviceBuffer + memcpy( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); + } + + // Convert callback buffer to stream sample rate + renderResampler->Convert( convBuffer, + stream_.deviceBuffer, + stream_.bufferSize, + convBufferSize ); } - // Convert callback buffer to stream sample rate - convertBufferWasapi( convBuffer, - stream_.deviceBuffer, - stream_.nDeviceChannels[OUTPUT], - stream_.sampleRate, - renderFormat->nSamplesPerSec, - stream_.bufferSize, - convBufferSize, - stream_.deviceFormat[OUTPUT] ); - // Push callback buffer into outputBuffer callbackPushed = renderBuffer.pushBuffer( convBuffer, convBufferSize * stream_.nDeviceChannels[OUTPUT], @@ -5134,7 +5336,7 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { // if the callback input buffer was not pulled from captureBuffer, wait for next capture event if ( !callbackPulled ) { - WaitForSingleObject( captureEvent, INFINITE ); + WaitForSingleObject( loopbackEnabled ? renderEvent : captureEvent, INFINITE ); } // Get capture buffer from stream @@ -5142,7 +5344,7 @@ void RtApiWasapi::wasapiThread() &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } @@ -5155,7 +5357,7 @@ void RtApiWasapi::wasapiThread() // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5164,7 +5366,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5174,7 +5376,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5196,13 +5398,13 @@ void RtApiWasapi::wasapiThread() // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } @@ -5211,7 +5413,7 @@ void RtApiWasapi::wasapiThread() if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } @@ -5224,7 +5426,7 @@ void RtApiWasapi::wasapiThread() // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5233,7 +5435,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5243,7 +5445,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5251,9 +5453,9 @@ void RtApiWasapi::wasapiThread() // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { + // unsetting the callbackPulled flag lets the stream know that + // the audio device is ready for another callback output buffer. callbackPulled = false; - // tick stream time - RtApi::tickStreamTime(); } } @@ -5264,16 +5466,19 @@ Exit: CoTaskMemFree( renderFormat ); free ( convBuffer ); + delete renderResampler; + delete captureResampler; CoUninitialize(); // update stream state stream_.state = STREAM_STOPPED; - if ( errorText_.empty() ) - return; - else + if ( !errorText.empty() ) + { + errorText_ = errorText; error( errorType ); + } } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -5289,6 +5494,8 @@ Exit: // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 +#include +#include #include #include #include @@ -6168,6 +6375,10 @@ void RtApiDs :: startStream() return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of @@ -6935,7 +7146,7 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) unsigned nDevices = 0; int result, subdevice, card; char name[64]; - snd_ctl_t *handle; + snd_ctl_t *handle = 0; // Count cards and devices card = -1; @@ -6944,6 +7155,7 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { + handle = 0; errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); @@ -6963,7 +7175,8 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) nDevices++; } nextcard: - snd_ctl_close( handle ); + if ( handle ) + snd_ctl_close( handle ); snd_card_next( &card ); } @@ -6984,7 +7197,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) unsigned nDevices = 0; int result, subdevice, card; char name[64]; - snd_ctl_t *chandle; + snd_ctl_t *chandle = 0; // Count cards and devices card = -1; @@ -6994,6 +7207,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) 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 ); @@ -7016,7 +7230,8 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) nDevices++; } nextcard: - snd_ctl_close( chandle ); + if ( chandle ) + snd_ctl_close( chandle ); snd_card_next( &card ); } @@ -7325,10 +7540,12 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); + snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } + snd_ctl_close( chandle ); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. @@ -7728,30 +7945,41 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); - #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { - // We previously attempted to increase the audio callback priority - // to SCHED_RR here via the attributes. However, while no errors - // were reported in doing so, it did not work. So, now this is - // done in the alsaCallbackHandler function. stream_.callbackInfo.doRealtime = true; + struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; - stream_.callbackInfo.priority = priority; + param.sched_priority = priority; + + // Set the policy BEFORE the priority. Otherwise it fails. + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + // This is definitely required. Otherwise it fails. + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedparam(&attr, ¶m); } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { - stream_.callbackInfo.isRunning = false; - errorText_ = "RtApiAlsa::error creating callback thread!"; - goto error; + // Failed. Try instead with default attributes. + result = pthread_create( &stream_.callbackInfo.thread, NULL, alsaCallbackHandler, &stream_.callbackInfo ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiAlsa::error creating callback thread!"; + goto error; + } } } @@ -7847,6 +8075,10 @@ void RtApiAlsa :: startStream() 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; @@ -8168,9 +8400,9 @@ static void *alsaCallbackHandler( void *ptr ) #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { - pthread_t tID = pthread_self(); // ID of this thread - sched_param prio = { info->priority }; // scheduling priority of thread - pthread_setschedparam( tID, SCHED_RR, &prio ); + std::cerr << "RtAudio alsa: " << + (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << + "running realtime scheduling" << std::endl; } #endif @@ -8253,7 +8485,15 @@ static void *pulseaudio_callback( void * user ) CallbackInfo *cbi = static_cast( user ); RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; - + +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if (cbi->doRealtime) { + std::cerr << "RtAudio pulse: " << + (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << + "running realtime scheduling" << std::endl; + } +#endif + while ( *isRunning ) { pthread_testcancel(); context->callbackEvent(); @@ -8410,6 +8650,10 @@ void RtApiPulse::startStream( void ) MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + stream_.state = STREAM_RUNNING; pah->runnable = true; @@ -8638,15 +8882,56 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, if ( !stream_.callbackInfo.isRunning ) { stream_.callbackInfo.object = this; + + stream_.state = STREAM_STOPPED; + // Set the thread attributes for joinable and realtime scheduling + // priority (optional). The higher priority will only take affect + // if the program is run as root or suid. Note, under Linux + // processes with CAP_SYS_NICE privilege, a user can change + // scheduling policy and priority (thus need not be root). See + // POSIX "capabilities". + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + stream_.callbackInfo.doRealtime = true; + struct sched_param param; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + param.sched_priority = priority; + + // Set the policy BEFORE the priority. Otherwise it fails. + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + // This is definitely required. Otherwise it fails. + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedparam(&attr, ¶m); + } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#endif + stream_.callbackInfo.isRunning = true; - if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { - errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; - goto error; + int result = pthread_create( &pah->thread, &attr, pulseaudio_callback, (void *)&stream_.callbackInfo); + pthread_attr_destroy(&attr); + if(result != 0) { + // Failed. Try instead with default attributes. + result = pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo); + if(result != 0) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; + goto error; + } } } - stream_.state = STREAM_STOPPED; - return true; + return SUCCESS; error: if ( pah && stream_.callbackInfo.isRunning ) { @@ -8667,6 +8952,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -9230,6 +9516,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); @@ -9237,8 +9524,13 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; - pthread_attr_setschedparam( &attr, ¶m ); - pthread_attr_setschedpolicy( &attr, SCHED_RR ); + + // Set the policy BEFORE the priority. Otherwise it fails. + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + // This is definitely required. Otherwise it fails. + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); @@ -9250,9 +9542,13 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { - stream_.callbackInfo.isRunning = false; - errorText_ = "RtApiOss::error creating callback thread!"; - goto error; + // Failed. Try instead with default attributes. + result = pthread_create( &stream_.callbackInfo.thread, NULL, ossCallbackHandler, &stream_.callbackInfo ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiOss::error creating callback thread!"; + goto error; + } } } @@ -9279,6 +9575,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -9341,6 +9638,10 @@ void RtApiOss :: startStream() MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts @@ -9608,6 +9909,14 @@ static void *ossCallbackHandler( void *ptr ) RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if (info->doRealtime) { + std::cerr << "RtAudio oss: " << + (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << + "running realtime scheduling" << std::endl; + } +#endif + while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); diff --git a/src/RtMidi.cpp b/src/RtMidi.cpp index 9a88e88..fb16ac4 100644 --- a/src/RtMidi.cpp +++ b/src/RtMidi.cpp @@ -5,7 +5,8 @@ This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. - RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ + RtMidi GitHub site: https://github.com/thestk/rtmidi + RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2019 Gary P. Scavone @@ -47,8 +48,227 @@ #endif // Default for Windows is to add an identifier to the port names; this -// flag can be undefined to disable this behaviour. -#define RTMIDI_ENSURE_UNIQUE_PORTNAMES +// flag can be defined (e.g. in your project file) to disable this behaviour. +//#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + +// **************************************************************** // +// +// MidiInApi and MidiOutApi subclass prototypes. +// +// **************************************************************** // + +#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) + #define __RTMIDI_DUMMY__ +#endif + +#if defined(__MACOSX_CORE__) + +class MidiInCore: public MidiInApi +{ + public: + MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInCore( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + void initialize( const std::string& clientName ); +}; + +class MidiOutCore: public MidiOutApi +{ + public: + MidiOutCore( const std::string &clientName ); + ~MidiOutCore( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__UNIX_JACK__) + +class MidiInJack: public MidiInApi +{ + public: + MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInJack( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + std::string clientName; + + void connect( void ); + void initialize( const std::string& clientName ); +}; + +class MidiOutJack: public MidiOutApi +{ + public: + MidiOutJack( const std::string &clientName ); + ~MidiOutJack( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + std::string clientName; + + void connect( void ); + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__LINUX_ALSA__) + +class MidiInAlsa: public MidiInApi +{ + public: + MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInAlsa( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + void initialize( const std::string& clientName ); +}; + +class MidiOutAlsa: public MidiOutApi +{ + public: + MidiOutAlsa( const std::string &clientName ); + ~MidiOutAlsa( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__WINDOWS_MM__) + +class MidiInWinMM: public MidiInApi +{ + public: + MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInWinMM( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + void initialize( const std::string& clientName ); +}; + +class MidiOutWinMM: public MidiOutApi +{ + public: + MidiOutWinMM( const std::string &clientName ); + ~MidiOutWinMM( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__RTMIDI_DUMMY__) + +class MidiInDummy: public MidiInApi +{ + public: + MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } + RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} + void openVirtualPort( const std::string &/*portName*/ ) {} + void closePort( void ) {} + void setClientName( const std::string &/*clientName*/ ) {}; + void setPortName( const std::string &/*portName*/ ) {}; + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } + + protected: + void initialize( const std::string& /*clientName*/ ) {} +}; + +class MidiOutDummy: public MidiOutApi +{ + public: + MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } + RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} + void openVirtualPort( const std::string &/*portName*/ ) {} + void closePort( void ) {} + void setClientName( const std::string &/*clientName*/ ) {}; + void setPortName( const std::string &/*portName*/ ) {}; + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } + void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {} + + protected: + void initialize( const std::string& /*clientName*/ ) {} +}; + +#endif //*********************************************************************// // RtMidi Definitions @@ -70,29 +290,92 @@ std::string RtMidi :: getVersion( void ) throw() return std::string( RTMIDI_VERSION ); } -void RtMidi :: getCompiledApi( std::vector &apis ) throw() -{ - apis.clear(); +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char* rtmidi_api_names[][2] = { + { "unspecified" , "Unknown" }, + { "core" , "CoreMidi" }, + { "alsa" , "ALSA" }, + { "jack" , "Jack" }, + { "winmm" , "Windows MultiMedia" }, + { "dummy" , "Dummy" }, +}; +const unsigned int rtmidi_num_api_names = + sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]); - // The order here will control the order of RtMidi's API search in - // the constructor. +// The order here will control the order of RtMidi's API search in +// the constructor. +extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { #if defined(__MACOSX_CORE__) - apis.push_back( MACOSX_CORE ); + RtMidi::MACOSX_CORE, #endif #if defined(__LINUX_ALSA__) - apis.push_back( LINUX_ALSA ); + RtMidi::LINUX_ALSA, #endif #if defined(__UNIX_JACK__) - apis.push_back( UNIX_JACK ); + RtMidi::UNIX_JACK, #endif #if defined(__WINDOWS_MM__) - apis.push_back( WINDOWS_MM ); + RtMidi::WINDOWS_MM, #endif #if defined(__RTMIDI_DUMMY__) - apis.push_back( RTMIDI_DUMMY ); + RtMidi::RTMIDI_DUMMY, #endif + RtMidi::UNSPECIFIED, +}; +extern "C" const unsigned int rtmidi_num_compiled_apis = + sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1; } +// This is a compile-time check that rtmidi_num_api_names == RtMidi::NUM_APIS. +// If the build breaks here, check that they match. +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +class StaticAssertions { StaticAssertions() { + StaticAssert(); +}}; + +void RtMidi :: getCompiledApi( std::vector &apis ) throw() +{ + apis = std::vector(rtmidi_compiled_apis, + rtmidi_compiled_apis + rtmidi_num_compiled_apis); +} + +std::string RtMidi :: getApiName( RtMidi::Api api ) +{ + if (api < 0 || api >= RtMidi::NUM_APIS) + return ""; + return rtmidi_api_names[api][0]; +} + +std::string RtMidi :: getApiDisplayName( RtMidi::Api api ) +{ + if (api < 0 || api >= RtMidi::NUM_APIS) + return "Unknown"; + return rtmidi_api_names[api][1]; +} + +RtMidi::Api RtMidi :: getCompiledApiByName( const std::string &name ) +{ + unsigned int i=0; + for (i = 0; i < rtmidi_num_compiled_apis; ++i) + if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0]) + return rtmidi_compiled_apis[i]; + return RtMidi::UNSPECIFIED; +} + +void RtMidi :: setClientName( const std::string &clientName ) +{ + rtapi_->setClientName( clientName ); +} + +void RtMidi :: setPortName( const std::string &portName ) +{ + rtapi_->setPortName( portName ); +} + + //*********************************************************************// // RtMidiIn Definitions //*********************************************************************// @@ -124,7 +407,7 @@ void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, un #endif } -RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) +RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) : RtMidi() { if ( api != UNSPECIFIED ) { @@ -192,7 +475,7 @@ void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) #endif } -RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName) +RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName) { if ( api != UNSPECIFIED ) { // Attempt to open the specified API. @@ -256,7 +539,7 @@ void MidiApi :: error( RtMidiError::Type type, std::string errorString ) firstErrorOccurred_ = true; const std::string errorMessage = errorString; - errorCallback_( type, errorMessage, errorCallbackUserData_); + errorCallback_( type, errorMessage, errorCallbackUserData_ ); firstErrorOccurred_ = false; return; } @@ -345,38 +628,38 @@ double MidiInApi :: getMessage( std::vector *message ) } double timeStamp; - if (!inputData_.queue.pop(message, &timeStamp)) + if ( !inputData_.queue.pop( message, &timeStamp ) ) return 0.0; return timeStamp; } -unsigned int MidiInApi::MidiQueue::size(unsigned int *__back, - unsigned int *__front) +unsigned int MidiInApi::MidiQueue::size( unsigned int *__back, + unsigned int *__front ) { // Access back/front members exactly once and make stack copies for // size calculation unsigned int _back = back, _front = front, _size; - if (_back >= _front) + if ( _back >= _front ) _size = _back - _front; else _size = ringSize - _front + _back; // Return copies of back/front so no new and unsynchronized accesses // to member variables are needed. - if (__back) *__back = _back; - if (__front) *__front = _front; + if ( __back ) *__back = _back; + if ( __front ) *__front = _front; return _size; } // As long as we haven't reached our queue size limit, push the message. -bool MidiInApi::MidiQueue::push(const MidiInApi::MidiMessage& msg) +bool MidiInApi::MidiQueue::push( const MidiInApi::MidiMessage& msg ) { // Local stack copies of front/back unsigned int _back, _front, _size; // Get back/front indexes exactly once and calculate current size - _size = size(&_back, &_front); + _size = size( &_back, &_front ); if ( _size < ringSize-1 ) { @@ -388,15 +671,15 @@ bool MidiInApi::MidiQueue::push(const MidiInApi::MidiMessage& msg) return false; } -bool MidiInApi::MidiQueue::pop(std::vector *msg, double* timeStamp) +bool MidiInApi::MidiQueue::pop( std::vector *msg, double* timeStamp ) { // Local stack copies of front/back unsigned int _back, _front, _size; // Get back/front indexes exactly once and calculate current size - _size = size(&_back, &_front); + _size = size( &_back, &_front ); - if (_size == 0) + if ( _size == 0 ) return false; // Copy queued message to the vector pointer argument and then "pop" it. @@ -479,10 +762,12 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * // function. nBytes = packet->length; - if ( nBytes == 0 ) continue; + if ( nBytes == 0 ) { + packet = MIDIPacketNext( packet ); + continue; + } // Calculate time stamp. - if ( data->firstMessage ) { message.timeStamp = 0.0; data->firstMessage = false; @@ -497,11 +782,10 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * if ( !continueSysex ) message.timeStamp = time * 0.000000001; } - apiData->lastTime = packet->timeStamp; - if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages - apiData->lastTime = AudioGetCurrentHostTime(); - } - //std::cout << "TimeStamp = " << packet->timeStamp << std::endl; + + // Track whether any non-filtered messages were found in this + // packet for timestamp calculation + bool foundNonFiltered = false; iByte = 0; if ( continueSysex ) { @@ -521,7 +805,7 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * } else { // As long as we haven't reached our queue size limit, push the message. - if (!data->queue.push(message)) + if ( !data->queue.push( message ) ) std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; } message.bytes.clear(); @@ -547,12 +831,12 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * continueSysex = packet->data[nBytes-1] != 0xF7; } else if ( status == 0xF1 ) { - // A MIDI time code message - if ( data->ignoreFlags & 0x02 ) { + // A MIDI time code message + if ( data->ignoreFlags & 0x02 ) { size = 0; iByte += 2; - } - else size = 2; + } + else size = 2; } else if ( status == 0xF2 ) size = 3; else if ( status == 0xF3 ) size = 2; @@ -570,6 +854,7 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * // Copy the MIDI data to our vector. if ( size ) { + foundNonFiltered = true; message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); if ( !continueSysex ) { // If not a continuing sysex message, invoke the user callback function or queue the message. @@ -579,7 +864,7 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * } else { // As long as we haven't reached our queue size limit, push the message. - if (!data->queue.push(message)) + if ( !data->queue.push( message ) ) std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; } message.bytes.clear(); @@ -588,19 +873,29 @@ static void midiInputCallback( const MIDIPacketList *list, void *procRef, void * } } } + + // Save the time of the last non-filtered message + if ( foundNonFiltered ) { + apiData->lastTime = packet->timeStamp; + if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages + apiData->lastTime = AudioGetCurrentHostTime(); + } + } + packet = MIDIPacketNext(packet); } } -MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) +MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) { - initialize( clientName ); + MidiInCore::initialize( clientName ); } MidiInCore :: ~MidiInCore( void ) { // Close a connection if it exists. - closePort(); + MidiInCore::closePort(); // Cleanup. CoreMidiData *data = static_cast (apiData_); @@ -629,7 +924,7 @@ void MidiInCore :: initialize( const std::string& clientName ) data->endpoint = 0; apiData_ = (void *) data; inputData_.apiData = (void *) data; - CFRelease(name); + CFRelease( name ); } void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName ) @@ -642,7 +937,7 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string &portNam CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nSrc = MIDIGetNumberOfSources(); - if (nSrc < 1) { + if ( nSrc < 1 ) { errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; @@ -658,9 +953,12 @@ void MidiInCore :: openPort( unsigned int portNumber, const std::string &portNam MIDIPortRef port; CoreMidiData *data = static_cast (apiData_); - OSStatus result = MIDIInputPortCreate( data->client, - CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIInputPortCreate( data->client, + portNameRef, midiInputCallback, (void *)&inputData_, &port ); + CFRelease( portNameRef ); + if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; @@ -700,9 +998,12 @@ void MidiInCore :: openVirtualPort( const std::string &portName ) // Create a virtual MIDI input destination. MIDIEndpointRef endpoint; + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIDestinationCreate( data->client, - CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), + portNameRef, midiInputCallback, (void *)&inputData_, &endpoint ); + CFRelease( portNameRef ); + if ( result != noErr ) { errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; error( RtMidiError::DRIVER_ERROR, errorString_ ); @@ -730,6 +1031,22 @@ void MidiInCore :: closePort( void ) connected_ = false; } +void MidiInCore :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInCore :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + unsigned int MidiInCore :: getPortCount() { CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); @@ -792,12 +1109,13 @@ CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) // does the entity name already start with the device name? // (some drivers do this though they shouldn't) // if so, do not prepend - if ( CFStringCompareWithOptions( result, /* endpoint name */ - str /* device name */, - CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { + if ( CFStringCompareWithOptions( result, /* endpoint name */ + str /* device name */, + CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { // prepend the device name to the entity name if ( CFStringGetLength( result ) > 0 ) CFStringInsert( result, 0, CFSTR(" ") ); + CFStringInsert( result, 0, str ); } CFRelease( str ); @@ -833,7 +1151,7 @@ static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType ); if ( err == noErr ) { if ( connObjectType == kMIDIObjectType_ExternalSource || - connObjectType == kMIDIObjectType_ExternalDestination ) { + connObjectType == kMIDIObjectType_ExternalDestination ) { // Connected to an external device's endpoint (10.3 and later). str = EndpointName( (MIDIEndpointRef)(connObject), true ); } else { @@ -844,7 +1162,8 @@ static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) if ( str != NULL ) { if ( anyStrings ) CFStringAppend( result, CFSTR(", ") ); - else anyStrings = true; + else + anyStrings = true; CFStringAppend( result, str ); CFRelease( str ); } @@ -858,7 +1177,7 @@ static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) CFRelease( result ); - // Here, either the endpoint had no connections, or we failed to obtain names + // Here, either the endpoint had no connections, or we failed to obtain names return EndpointName( endpoint, false ); } @@ -879,8 +1198,8 @@ std::string MidiInCore :: getPortName( unsigned int portNumber ) } portRef = MIDIGetSource( portNumber ); - nameRef = ConnectedEndpointName(portRef); - CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8); + nameRef = ConnectedEndpointName( portRef ); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); CFRelease( nameRef ); return stringName = name; @@ -891,15 +1210,16 @@ std::string MidiInCore :: getPortName( unsigned int portNumber ) // Class Definitions: MidiOutCore //*********************************************************************// -MidiOutCore :: MidiOutCore( const std::string &clientName ) : MidiOutApi() +MidiOutCore :: MidiOutCore( const std::string &clientName ) + : MidiOutApi() { - initialize( clientName ); + MidiOutCore::initialize( clientName ); } MidiOutCore :: ~MidiOutCore( void ) { // Close a connection if it exists. - closePort(); + MidiOutCore::closePort(); // Cleanup. CoreMidiData *data = static_cast (apiData_); @@ -956,7 +1276,7 @@ std::string MidiOutCore :: getPortName( unsigned int portNumber ) nameRef = ConnectedEndpointName(portRef); CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); CFRelease( nameRef ); - + return stringName = name; } @@ -987,9 +1307,7 @@ void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portNa MIDIPortRef port; CoreMidiData *data = static_cast (apiData_); CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); - OSStatus result = MIDIOutputPortCreate( data->client, - portNameRef, - &port ); + OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); CFRelease( portNameRef ); if ( result != noErr ) { MIDIClientDispose( data->client ); @@ -1031,6 +1349,22 @@ void MidiOutCore :: closePort( void ) connected_ = false; } +void MidiOutCore :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutCore :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + void MidiOutCore :: openVirtualPort( const std::string &portName ) { CoreMidiData *data = static_cast (apiData_); @@ -1043,9 +1377,10 @@ void MidiOutCore :: openVirtualPort( const std::string &portName ) // Create a virtual MIDI output source. MIDIEndpointRef endpoint; - OSStatus result = MIDISourceCreate( data->client, - CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), - &endpoint ); + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint ); + CFRelease( portNameRef ); + if ( result != noErr ) { errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; error( RtMidiError::DRIVER_ERROR, errorString_ ); @@ -1062,7 +1397,7 @@ void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) // messages. Otherwise, we use a single CoreMidi MIDIPacket. unsigned int nBytes = static_cast (size); if ( nBytes == 0 ) { - errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; + errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; error( RtMidiError::WARNING, errorString_ ); return; } @@ -1077,21 +1412,21 @@ void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) return; } - Byte buffer[nBytes+(sizeof(MIDIPacketList))]; - ByteCount listSize = sizeof(buffer); + Byte buffer[nBytes+(sizeof( MIDIPacketList ))]; + ByteCount listSize = sizeof( buffer ); MIDIPacketList *packetList = (MIDIPacketList*)buffer; MIDIPacket *packet = MIDIPacketListInit( packetList ); ByteCount remainingBytes = nBytes; - while (remainingBytes && packet) { + while ( remainingBytes && packet ) { ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes]; - packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr); - remainingBytes -= bytesForPacket; + packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr ); + remainingBytes -= bytesForPacket; } if ( !packet ) { - errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; + errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } @@ -1272,7 +1607,7 @@ static void *alsaMidiHandler( void *ptr ) if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; break; - case SND_SEQ_EVENT_SYSEX: + case SND_SEQ_EVENT_SYSEX: if ( (data->ignoreFlags & 0x01) ) break; if ( ev->data.ext.len > apiData->bufferSize ) { apiData->bufferSize = ev->data.ext.len; @@ -1284,6 +1619,8 @@ static void *alsaMidiHandler( void *ptr ) break; } } + doDecode = true; + break; default: doDecode = true; @@ -1320,21 +1657,25 @@ static void *alsaMidiHandler( void *ptr ) // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html // Perform the carry for the later subtraction by updating y. - snd_seq_real_time_t &x(ev->time.time); - snd_seq_real_time_t &y(apiData->lastTime); - if (x.tv_nsec < y.tv_nsec) { - int nsec = (y.tv_nsec - x.tv_nsec) / 1000000000 + 1; + // Temp var y is timespec because computation requires signed types, + // while snd_seq_real_time_t has unsigned types. + snd_seq_real_time_t &x( ev->time.time ); + struct timespec y; + y.tv_nsec = apiData->lastTime.tv_nsec; + y.tv_sec = apiData->lastTime.tv_sec; + if ( x.tv_nsec < y.tv_nsec ) { + int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1; y.tv_nsec -= 1000000000 * nsec; y.tv_sec += nsec; } - if (x.tv_nsec - y.tv_nsec > 1000000000) { - int nsec = (x.tv_nsec - y.tv_nsec) / 1000000000; + if ( x.tv_nsec - y.tv_nsec > 1000000000 ) { + int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000; y.tv_nsec += 1000000000 * nsec; y.tv_sec -= nsec; } // Compute the time difference. - time = x.tv_sec - y.tv_sec + (x.tv_nsec - y.tv_nsec)*1e-9; + time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9; apiData->lastTime = ev->time.time; @@ -1360,7 +1701,7 @@ static void *alsaMidiHandler( void *ptr ) } else { // As long as we haven't reached our queue size limit, push the message. - if (!data->queue.push(message)) + if ( !data->queue.push( message ) ) std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; } } @@ -1372,21 +1713,22 @@ static void *alsaMidiHandler( void *ptr ) return 0; } -MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) +MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) { - initialize( clientName ); + MidiInAlsa::initialize( clientName ); } MidiInAlsa :: ~MidiInAlsa() { // Close a connection if it exists. - closePort(); + MidiInAlsa::closePort(); // Shutdown the input thread. AlsaMidiData *data = static_cast (apiData_); if ( inputData_.doInput ) { inputData_.doInput = false; - int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); + int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); (void) res; if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); @@ -1407,7 +1749,7 @@ void MidiInAlsa :: initialize( const std::string& clientName ) { // Set up the ALSA sequencer client. snd_seq_t *seq; - int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); + int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK ); if ( result < 0 ) { errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); @@ -1430,7 +1772,7 @@ void MidiInAlsa :: initialize( const std::string& clientName ) apiData_ = (void *) data; inputData_.apiData = (void *) data; - if ( pipe(data->trigger_fds) == -1 ) { + if ( pipe(data->trigger_fds) == -1 ) { errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -1438,14 +1780,14 @@ void MidiInAlsa :: initialize( const std::string& clientName ) // Create the input queue #ifndef AVOID_TIMESTAMPING - data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); + data->queue_id = snd_seq_alloc_named_queue( seq, "RtMidi Queue" ); // Set arbitrary tempo (mm=100) and resolution (240) snd_seq_queue_tempo_t *qtempo; - snd_seq_queue_tempo_alloca(&qtempo); - snd_seq_queue_tempo_set_tempo(qtempo, 600000); - snd_seq_queue_tempo_set_ppq(qtempo, 240); - snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); - snd_seq_drain_output(data->seq); + snd_seq_queue_tempo_alloca( &qtempo ); + snd_seq_queue_tempo_set_tempo( qtempo, 600000 ); + snd_seq_queue_tempo_set_ppq( qtempo, 240 ); + snd_seq_set_queue_tempo( data->seq, data->queue_id, qtempo ); + snd_seq_drain_output( data->seq ); #endif } @@ -1469,7 +1811,7 @@ unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) && ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue; - + unsigned int caps = snd_seq_port_info_get_capability( pinfo ); if ( ( caps & type ) != type ) continue; if ( count == portNumber ) return 1; @@ -1565,33 +1907,33 @@ void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portNam SND_SEQ_PORT_TYPE_APPLICATION ); snd_seq_port_info_set_midi_channels(pinfo, 16); #ifndef AVOID_TIMESTAMPING - snd_seq_port_info_set_timestamping(pinfo, 1); - snd_seq_port_info_set_timestamp_real(pinfo, 1); - snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); + snd_seq_port_info_set_timestamping( pinfo, 1 ); + snd_seq_port_info_set_timestamp_real( pinfo, 1 ); + snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); #endif - snd_seq_port_info_set_name(pinfo, portName.c_str() ); - data->vport = snd_seq_create_port(data->seq, pinfo); - + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + data->vport = snd_seq_create_port( data->seq, pinfo ); + if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } - data->vport = snd_seq_port_info_get_port(pinfo); + data->vport = snd_seq_port_info_get_port( pinfo ); } receiver.port = data->vport; if ( !data->subscription ) { // Make subscription - if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { + if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } - snd_seq_port_subscribe_set_sender(data->subscription, &sender); - snd_seq_port_subscribe_set_dest(data->subscription, &receiver); - if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { + snd_seq_port_subscribe_set_sender( data->subscription, &sender ); + snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); + if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; @@ -1608,13 +1950,13 @@ void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portNam #endif // Start our MIDI input thread. pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); inputData_.doInput = true; - int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); - pthread_attr_destroy(&attr); + int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); + pthread_attr_destroy( &attr ); if ( err ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); @@ -1636,31 +1978,31 @@ void MidiInAlsa :: openVirtualPort( const std::string &portName ) snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); snd_seq_port_info_set_capability( pinfo, - SND_SEQ_PORT_CAP_WRITE | - SND_SEQ_PORT_CAP_SUBS_WRITE ); + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE ); snd_seq_port_info_set_type( pinfo, - SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION ); - snd_seq_port_info_set_midi_channels(pinfo, 16); + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION ); + snd_seq_port_info_set_midi_channels( pinfo, 16 ); #ifndef AVOID_TIMESTAMPING - snd_seq_port_info_set_timestamping(pinfo, 1); - snd_seq_port_info_set_timestamp_real(pinfo, 1); - snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); + snd_seq_port_info_set_timestamping( pinfo, 1 ); + snd_seq_port_info_set_timestamp_real( pinfo, 1 ); + snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); #endif - snd_seq_port_info_set_name(pinfo, portName.c_str()); - data->vport = snd_seq_create_port(data->seq, pinfo); + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + data->vport = snd_seq_create_port( data->seq, pinfo ); if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } - data->vport = snd_seq_port_info_get_port(pinfo); + data->vport = snd_seq_port_info_get_port( pinfo ); } if ( inputData_.doInput == false ) { // Wait for old thread to stop, if still running - if ( !pthread_equal(data->thread, data->dummy_thread_id) ) + if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) pthread_join( data->thread, NULL ); // Start the input queue @@ -1670,13 +2012,13 @@ void MidiInAlsa :: openVirtualPort( const std::string &portName ) #endif // Start our MIDI input thread. pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); inputData_.doInput = true; - int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); - pthread_attr_destroy(&attr); + int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); + pthread_attr_destroy( &attr ); if ( err ) { if ( data->subscription ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); @@ -1712,13 +2054,31 @@ void MidiInAlsa :: closePort( void ) // Stop thread to avoid triggering the callback, while the port is intended to be closed if ( inputData_.doInput ) { inputData_.doInput = false; - int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); + int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); (void) res; - if ( !pthread_equal(data->thread, data->dummy_thread_id) ) + if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) pthread_join( data->thread, NULL ); } } +void MidiInAlsa :: setClientName( const std::string &clientName ) +{ + + AlsaMidiData *data = static_cast ( apiData_ ); + snd_seq_set_client_name( data->seq, clientName.c_str() ); + +} + +void MidiInAlsa :: setPortName( const std::string &portName ) +{ + AlsaMidiData *data = static_cast (apiData_); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + snd_seq_get_port_info( data->seq, data->vport, pinfo ); + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + snd_seq_set_port_info( data->seq, data->vport, pinfo ); +} + //*********************************************************************// // API: LINUX ALSA // Class Definitions: MidiOutAlsa @@ -1726,13 +2086,13 @@ void MidiInAlsa :: closePort( void ) MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi() { - initialize( clientName ); + MidiOutAlsa::initialize( clientName ); } MidiOutAlsa :: ~MidiOutAlsa() { // Close a connection if it exists. - closePort(); + MidiOutAlsa::closePort(); // Cleanup. AlsaMidiData *data = static_cast (apiData_); @@ -1752,7 +2112,7 @@ void MidiOutAlsa :: initialize( const std::string& clientName ) errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; - } + } // Set client name. snd_seq_set_client_name( seq, clientName.c_str() ); @@ -1785,8 +2145,8 @@ void MidiOutAlsa :: initialize( const std::string& clientName ) unsigned int MidiOutAlsa :: getPortCount() { - snd_seq_port_info_t *pinfo; - snd_seq_port_info_alloca( &pinfo ); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); @@ -1802,16 +2162,16 @@ std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) std::string stringName; AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { - int cnum = snd_seq_port_info_get_client(pinfo); + int cnum = snd_seq_port_info_get_client( pinfo ); snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; - os << snd_seq_client_info_get_name(cinfo); + os << snd_seq_client_info_get_name( cinfo ); os << ":"; os << snd_seq_port_info_get_name( pinfo ); os << " "; // These lines added to make sure devices are listed os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; - os << snd_seq_port_info_get_port(pinfo); + os << snd_seq_port_info_get_port( pinfo ); stringName = os.str(); return stringName; } @@ -1831,14 +2191,14 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portNa } unsigned int nSrc = this->getPortCount(); - if (nSrc < 1) { + if ( nSrc < 1 ) { errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } - snd_seq_port_info_t *pinfo; - snd_seq_port_info_alloca( &pinfo ); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { std::ostringstream ost; @@ -1867,17 +2227,17 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portNa sender.port = data->vport; // Make subscription - if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { + if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } - snd_seq_port_subscribe_set_sender(data->subscription, &sender); - snd_seq_port_subscribe_set_dest(data->subscription, &receiver); - snd_seq_port_subscribe_set_time_update(data->subscription, 1); - snd_seq_port_subscribe_set_time_real(data->subscription, 1); - if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { + snd_seq_port_subscribe_set_sender( data->subscription, &sender ); + snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); + snd_seq_port_subscribe_set_time_update( data->subscription, 1 ); + snd_seq_port_subscribe_set_time_real( data->subscription, 1 ); + if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; error( RtMidiError::DRIVER_ERROR, errorString_ ); @@ -1898,6 +2258,24 @@ void MidiOutAlsa :: closePort( void ) } } +void MidiOutAlsa :: setClientName( const std::string &clientName ) +{ + + AlsaMidiData *data = static_cast ( apiData_ ); + snd_seq_set_client_name( data->seq, clientName.c_str() ); + +} + +void MidiOutAlsa :: setPortName( const std::string &portName ) +{ + AlsaMidiData *data = static_cast (apiData_); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + snd_seq_get_port_info( data->seq, data->vport, pinfo ); + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + snd_seq_set_port_info( data->seq, data->vport, pinfo ); +} + void MidiOutAlsa :: openVirtualPort( const std::string &portName ) { AlsaMidiData *data = static_cast (apiData_); @@ -1920,7 +2298,7 @@ void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) unsigned int nBytes = static_cast (size); if ( nBytes > data->bufferSize ) { data->bufferSize = nBytes; - result = snd_midi_event_resize_buffer ( data->coder, nBytes); + result = snd_midi_event_resize_buffer( data->coder, nBytes ); if ( result != 0 ) { errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; error( RtMidiError::DRIVER_ERROR, errorString_ ); @@ -1929,17 +2307,17 @@ void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) free (data->buffer); data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { - errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; - error( RtMidiError::MEMORY_ERROR, errorString_ ); - return; + errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; } } snd_seq_event_t ev; - snd_seq_ev_clear(&ev); - snd_seq_ev_set_source(&ev, data->vport); - snd_seq_ev_set_subs(&ev); - snd_seq_ev_set_direct(&ev); + snd_seq_ev_clear( &ev ); + snd_seq_ev_set_source( &ev, data->vport ); + snd_seq_ev_set_subs( &ev ); + snd_seq_ev_set_direct( &ev ); for ( unsigned int i=0; ibuffer[i] = message[i]; result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); if ( result < (int)nBytes ) { @@ -1949,13 +2327,13 @@ void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) } // Send the event. - result = snd_seq_event_output(data->seq, &ev); + result = snd_seq_event_output( data->seq, &ev ); if ( result < 0 ) { errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; error( RtMidiError::WARNING, errorString_ ); return; } - snd_seq_drain_output(data->seq); + snd_seq_drain_output( data->seq ); } #endif // __LINUX_ALSA__ @@ -2028,7 +2406,7 @@ struct WinMidiData { //*********************************************************************// static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, - UINT inputStatus, + UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage, DWORD timestamp ) @@ -2045,7 +2423,6 @@ static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, data->firstMessage = false; } else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; - apiData->lastTime = timestamp; if ( inputStatus == MIM_DATA ) { // Channel or system message @@ -2064,11 +2441,11 @@ static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, } else if ( status == 0xF2 ) nBytes = 3; else if ( status == 0xF3 ) nBytes = 2; - else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) { + else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { // A MIDI timing tick message and we're ignoring it. return; } - else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { + else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { // A MIDI active sensing message and we're ignoring it. return; } @@ -2078,8 +2455,8 @@ static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, for ( int i=0; imessage.bytes.push_back( *ptr++ ); } else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) - MIDIHDR *sysex = ( MIDIHDR *) midiMessage; - if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { + MIDIHDR *sysex = ( MIDIHDR *) midiMessage; + if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { // Sysex message and we're not ignoring it for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) apiData->message.bytes.push_back( sysex->lpData[i] ); @@ -2106,13 +2483,16 @@ static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, else return; } + // Save the time of the last non-filtered message + apiData->lastTime = timestamp; + if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. - if (!data->queue.push(apiData->message)) + if ( !data->queue.push( apiData->message ) ) std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n"; } @@ -2120,15 +2500,16 @@ static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, apiData->message.bytes.clear(); } -MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) +MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) { - initialize( clientName ); + MidiInWinMM::initialize( clientName ); } MidiInWinMM :: ~MidiInWinMM() { // Close a connection if it exists. - closePort(); + MidiInWinMM::closePort(); WinMidiData *data = static_cast (apiData_); DeleteCriticalSection( &(data->_mutex) ); @@ -2153,7 +2534,7 @@ void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) inputData_.apiData = (void *) data; data->message.bytes.clear(); // needs to be empty for first input message - if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) { + if ( !InitializeCriticalSectionAndSpinCount( &(data->_mutex), 0x00000400 ) ) { errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; error( RtMidiError::WARNING, errorString_ ); } @@ -2269,6 +2650,22 @@ void MidiInWinMM :: closePort( void ) } } +void MidiInWinMM :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInWinMM :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + unsigned int MidiInWinMM :: getPortCount() { return midiInGetNumDevs(); @@ -2290,10 +2687,10 @@ std::string MidiInWinMM :: getPortName( unsigned int portNumber ) midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); stringName = ConvertToUTF8( deviceCaps.szPname ); - // Next lines added to add the portNumber to the name so that + // Next lines added to add the portNumber to the name so that // the device's names are sure to be listed with individual names // even when they have the same brand name -#ifdef RTMIDI_ENSURE_UNIQUE_PORTNAMES +#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES std::ostringstream os; os << " "; os << portNumber; @@ -2310,13 +2707,13 @@ std::string MidiInWinMM :: getPortName( unsigned int portNumber ) MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi() { - initialize( clientName ); + MidiOutWinMM::initialize( clientName ); } MidiOutWinMM :: ~MidiOutWinMM() { // Close a connection if it exists. - closePort(); + MidiOutWinMM::closePort(); // Cleanup. WinMidiData *data = static_cast (apiData_); @@ -2356,14 +2753,14 @@ std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) } MIDIOUTCAPS deviceCaps; - midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); + midiOutGetDevCaps( portNumber, &deviceCaps, sizeof( MIDIOUTCAPS ) ); stringName = ConvertToUTF8( deviceCaps.szPname ); - // Next lines added to add the portNumber to the name so that + // Next lines added to add the portNumber to the name so that // the device's names are sure to be listed with individual names // even when they have the same brand name std::ostringstream os; -#ifdef RTMIDI_ENSURE_UNIQUE_PORTNAMES +#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES os << " "; os << portNumber; stringName += os.str(); @@ -2381,7 +2778,7 @@ void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*por } unsigned int nDevices = midiOutGetNumDevs(); - if (nDevices < 1) { + if ( nDevices < 1 ) { errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; @@ -2421,6 +2818,22 @@ void MidiOutWinMM :: closePort( void ) } } +void MidiOutWinMM :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutWinMM :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. @@ -2459,7 +2872,7 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) sysex.lpData = (LPSTR) buffer; sysex.dwBufferLength = nBytes; sysex.dwFlags = 0; - result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) ); + result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof( MIDIHDR ) ); if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; @@ -2468,7 +2881,7 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) } // Send the message. - result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) ); + result = midiOutLongMsg( data->outHandle, &sysex, sizeof( MIDIHDR ) ); if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; @@ -2477,7 +2890,7 @@ void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) } // Unprepare the buffer and MIDIHDR. - while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); + while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof ( MIDIHDR ) ) ) Sleep( 1 ); free( buffer ); } else { // Channel or system message. @@ -2556,36 +2969,71 @@ static int jackProcessIn( jack_nframes_t nframes, void *arg ) // Is port created? if ( jData->port == NULL ) return 0; + void *buff = jack_port_get_buffer( jData->port, nframes ); + bool& continueSysex = rtData->continueSysex; + unsigned char& ignoreFlags = rtData->ignoreFlags; // We have midi events in buffer int evCount = jack_midi_get_event_count( buff ); for (int j = 0; j < evCount; j++) { - MidiInApi::MidiMessage message; - message.bytes.clear(); - + MidiInApi::MidiMessage& message = rtData->message; jack_midi_event_get( &event, buff, j ); - for ( unsigned int i = 0; i < event.size; i++ ) - message.bytes.push_back( event.buffer[i] ); - // Compute the delta time. time = jack_get_time(); - if ( rtData->firstMessage == true ) + if ( rtData->firstMessage == true ) { + message.timeStamp = 0.0; rtData->firstMessage = false; - else + } else message.timeStamp = ( time - jData->lastTime ) * 0.000001; jData->lastTime = time; - if ( !rtData->continueSysex ) { + if ( !continueSysex ) + message.bytes.clear(); + + if ( !( ( continueSysex || event.buffer[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) { + // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx, + // copy the event buffer into the MIDI message struct. + for ( unsigned int i = 0; i < event.size; i++ ) + message.bytes.push_back( event.buffer[i] ); + } + + switch ( event.buffer[0] ) { + case 0xF0: + // Start of a SysEx message + continueSysex = event.buffer[event.size - 1] != 0xF7; + if ( ignoreFlags & 0x01 ) continue; + break; + case 0xF1: + case 0xF8: + // MIDI Time Code or Timing Clock message + if ( ignoreFlags & 0x02 ) continue; + break; + case 0xFE: + // Active Sensing message + if ( ignoreFlags & 0x04 ) continue; + break; + default: + if ( continueSysex ) { + // Continuation of a SysEx message + continueSysex = event.buffer[event.size - 1] != 0xF7; + if ( ignoreFlags & 0x01 ) continue; + } + // All other MIDI messages + } + + if ( !continueSysex ) { + // If not a continuation of a SysEx message, + // invoke the user callback function or queue the message. if ( rtData->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; callback( message.timeStamp, &message.bytes, rtData->userData ); } else { // As long as we haven't reached our queue size limit, push the message. - if (!rtData->queue.push(message)) + if ( !rtData->queue.push( message ) ) std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; } } @@ -2594,9 +3042,10 @@ static int jackProcessIn( jack_nframes_t nframes, void *arg ) return 0; } -MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) +MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) { - initialize( clientName ); + MidiInJack::initialize( clientName ); } void MidiInJack :: initialize( const std::string& clientName ) @@ -2632,7 +3081,7 @@ void MidiInJack :: connect() MidiInJack :: ~MidiInJack() { JackMidiData *data = static_cast (apiData_); - closePort(); + MidiInJack::closePort(); if ( data->client ) jack_client_close( data->client ); @@ -2646,11 +3095,11 @@ void MidiInJack :: openPort( unsigned int portNumber, const std::string &portNam connect(); // Creating new port - if ( data->port == NULL) + if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); - if ( data->port == NULL) { + if ( data->port == NULL ) { errorString_ = "MidiInJack::openPort: JACK error creating port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; @@ -2659,6 +3108,8 @@ void MidiInJack :: openPort( unsigned int portNumber, const std::string &portNam // Connecting to the output std::string name = getPortName( portNumber ); jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); + + connected_ = true; } void MidiInJack :: openVirtualPort( const std::string &portName ) @@ -2699,7 +3150,7 @@ unsigned int MidiInJack :: getPortCount() std::string MidiInJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); - std::string retStr(""); + std::string retStr( "" ); connect(); @@ -2714,7 +3165,9 @@ std::string MidiInJack :: getPortName( unsigned int portNumber ) return retStr; } - if ( ports[portNumber] == NULL ) { + unsigned int i; + for ( i=0; iport == NULL ) return; jack_port_unregister( data->client, data->port ); data->port = NULL; + + connected_ = false; +} + +void MidiInJack:: setClientName( const std::string& ) +{ + + errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInJack :: setPortName( const std::string &portName ) +{ + JackMidiData *data = static_cast (apiData_); +#ifdef JACK_HAS_PORT_RENAME + jack_port_rename( data->client, data->port, portName.c_str() ); +#else + jack_port_set_name( data->port, portName.c_str() ); +#endif } //*********************************************************************// @@ -2754,15 +3227,15 @@ static int jackProcessOut( jack_nframes_t nframes, void *arg ) jack_midi_clear_buffer( buff ); while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { - jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) ); + jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof( space ) ); midiData = jack_midi_event_reserve( buff, 0, space ); jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); } #ifdef HAVE_SEMAPHORE - if (!sem_trywait(&data->sem_needpost)) - sem_post(&data->sem_cleanup); + if ( !sem_trywait( &data->sem_needpost ) ) + sem_post( &data->sem_cleanup ); #endif return 0; @@ -2770,7 +3243,7 @@ static int jackProcessOut( jack_nframes_t nframes, void *arg ) MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi() { - initialize( clientName ); + MidiOutJack::initialize( clientName ); } void MidiOutJack :: initialize( const std::string& clientName ) @@ -2781,8 +3254,8 @@ void MidiOutJack :: initialize( const std::string& clientName ) data->port = NULL; data->client = NULL; #ifdef HAVE_SEMAPHORE - sem_init(&data->sem_cleanup, 0, 0); - sem_init(&data->sem_needpost, 0, 0); + sem_init( &data->sem_cleanup, 0, 0 ); + sem_init( &data->sem_needpost, 0, 0 ); #endif this->clientName = clientName; @@ -2794,13 +3267,13 @@ void MidiOutJack :: connect() JackMidiData *data = static_cast (apiData_); if ( data->client ) return; - - // Initialize output ringbuffers + + // Initialize output ringbuffers data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); // Initialize JACK client - if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { + if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) { errorString_ = "MidiOutJack::initialize: JACK server not running?"; error( RtMidiError::WARNING, errorString_ ); return; @@ -2813,8 +3286,8 @@ void MidiOutJack :: connect() MidiOutJack :: ~MidiOutJack() { JackMidiData *data = static_cast (apiData_); - closePort(); - + MidiOutJack::closePort(); + // Cleanup jack_ringbuffer_free( data->buffSize ); jack_ringbuffer_free( data->buffMessage ); @@ -2823,8 +3296,8 @@ MidiOutJack :: ~MidiOutJack() } #ifdef HAVE_SEMAPHORE - sem_destroy(&data->sem_cleanup); - sem_destroy(&data->sem_needpost); + sem_destroy( &data->sem_cleanup ); + sem_destroy( &data->sem_needpost ); #endif delete data; @@ -2839,7 +3312,7 @@ void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portNa // Creating new port if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), - JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openPort: JACK error creating port"; @@ -2850,6 +3323,8 @@ void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portNa // Connecting to the output std::string name = getPortName( portNumber ); jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); + + connected_ = true; } void MidiOutJack :: openVirtualPort( const std::string &portName ) @@ -2859,7 +3334,7 @@ void MidiOutJack :: openVirtualPort( const std::string &portName ) connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), - JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; @@ -2877,7 +3352,7 @@ unsigned int MidiOutJack :: getPortCount() // List of available ports const char **ports = jack_get_ports( data->client, NULL, - JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); if ( ports == NULL ) return 0; while ( ports[count] != NULL ) @@ -2897,16 +3372,16 @@ std::string MidiOutJack :: getPortName( unsigned int portNumber ) // List of available ports const char **ports = jack_get_ports( data->client, NULL, - JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); // Check port validity - if ( ports == NULL) { + if ( ports == NULL ) { errorString_ = "MidiOutJack::getPortName: no ports available!"; error( RtMidiError::WARNING, errorString_ ); return retStr; } - if ( ports[portNumber] == NULL) { + if ( ports[portNumber] == NULL ) { std::ostringstream ost; ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); @@ -2926,16 +3401,35 @@ void MidiOutJack :: closePort() #ifdef HAVE_SEMAPHORE struct timespec ts; - if (clock_gettime(CLOCK_REALTIME, &ts) != -1) - { + if ( clock_gettime( CLOCK_REALTIME, &ts ) != -1 ) { ts.tv_sec += 1; // wait max one second - sem_post(&data->sem_needpost); - sem_timedwait(&data->sem_cleanup, &ts); + sem_post( &data->sem_needpost ); + sem_timedwait( &data->sem_cleanup, &ts ); } #endif jack_port_unregister( data->client, data->port ); data->port = NULL; + + connected_ = false; +} + +void MidiOutJack:: setClientName( const std::string& ) +{ + + errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutJack :: setPortName( const std::string &portName ) +{ + JackMidiData *data = static_cast (apiData_); +#ifdef JACK_HAS_PORT_RENAME + jack_port_rename( data->client, data->port, portName.c_str() ); +#else + jack_port_set_name( data->port, portName.c_str() ); +#endif } void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) @@ -2944,8 +3438,7 @@ void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) JackMidiData *data = static_cast (apiData_); // Write full message to buffer - jack_ringbuffer_write( data->buffMessage, ( const char * ) message, - nBytes ); + jack_ringbuffer_write( data->buffMessage, ( const char * ) message, nBytes ); jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); }