RtMidi 0.6.1.0 → 0.7.0.0
raw patch · 8 files changed
+716/−172 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- README.md +9/−3
- RtMidi.cabal +7/−7
- Sound/RtMidi.hs +15/−8
- Sound/RtMidi/Foreign.hsc +1/−1
- rtmidi/RtMidi.cpp +596/−107
- rtmidi/RtMidi.h +33/−8
- rtmidi/rtmidi_c.cpp +42/−31
- rtmidi/rtmidi_c.h +13/−7
README.md view
@@ -2,6 +2,11 @@ Haskell wrapper for [RtMidi](http://www.music.mcgill.ca/~gary/rtmidi/), the lightweight, cross-platform MIDI I/O library. +## Version++The version of this library does not correspond to the RtMidi version (sorry). You can look at the `justfile` to see that we vendor sources+from version `5.0.0`, and we apply a small patch to fix API names (`rtmidi.patch`).+ ## How to use See [Hackage](https://hackage.haskell.org/package/RtMidi) for the latest released version and add `RtMidi` and `vector` to your library `build-depends`.@@ -14,15 +19,16 @@ This project is tested with Cabal (latest versions of last two compiler lines) and Stack (latest LTS). -You can get started with development like so:+You can get started with development like so (if you have `just` installed): # Build and test on OSX CoreMIDI support or Linux with ALSA support. # (You can also manually invoke stack and pass `--flag RtMidi:jack` for Jack support.)- make test+ just test # Print information about all accessible MIDI devices and all compiled APIs.- make example-report+ just example-report ## TODO * See if there is a way to autodetect Jack in the Cabal file.+* See if there is a way to make OSX not take 3 seconds when initializing the MIDI subsystem.
RtMidi.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: RtMidi-version: 0.6.1.0+version: 0.7.0.0 synopsis: Haskell wrapper for RtMidi, the lightweight, cross-platform MIDI I/O library. description: Please see the README on GitHub at <https://github.com/riottracker/RtMidi#readme> category: Sound@@ -46,21 +46,21 @@ if os(linux) if flag(alsa) && flag(jack)- cxx-options: -D__LINUX_ALSA__ -D__UNIX_JACK__+ cxx-options: -std=c++11 -D__LINUX_ALSA__ -D__UNIX_JACK__ extra-libraries: asound pthread jack if flag(alsa) && !flag(jack)- cxx-options: -D__LINUX_ALSA__+ cxx-options: -std=c++11 -D__LINUX_ALSA__ extra-libraries: asound pthread if !flag(alsa) && flag(jack)- cxx-options: -D__UNIX_JACK__+ cxx-options: -std=c++11 -D__UNIX_JACK__ extra-libraries: jack if os(darwin) if flag(jack)- cxx-options: -D__MACOSX_CORE__ -D__UNIX_JACK__+ cxx-options: -std=c++11 -DTARGET_OS_IPHONE=0-D__MACOSX_CORE__ -D__UNIX_JACK__ extra-libraries: jack else- cxx-options: -D__MACOSX_CORE__+ cxx-options: -std=c++11 -D__MACOSX_CORE__ frameworks: CoreMIDI CoreAudio CoreFoundation -- NOTE(ejconlon) This is to make the c ffi wrapper actually catch -- the c++ exceptions instead of simply aborting.@@ -70,7 +70,7 @@ ghc-options: -pgmc=clang++ if os(mingw32)- cxx-options: -D__WINDOWS_MM__+ cxx-options: -std=c++11 -D__WINDOWS_MM__ extra-libraries: winmm executable rtmidi-callback
Sound/RtMidi.hs view
@@ -46,8 +46,8 @@ import qualified Data.Vector.Storable as VS import qualified Data.Vector.Storable.Mutable as VSM import Data.Word (Word8)-import Foreign (FunPtr, Ptr, Storable (..), alloca, allocaArray, nullPtr, peekArray)-import Foreign.C (CDouble (..), CInt (..), CSize, CString, CUChar (..), peekCString, withCString)+import Foreign (FunPtr, Ptr, Storable (..), alloca, allocaArray, allocaBytes, nullPtr, peekArray)+import Foreign.C (CDouble (..), CInt (..), CSize, CString, CUChar (..), peekCString, peekCStringLen, withCString) import Foreign.ForeignPtr (ForeignPtr, newForeignPtr, withForeignPtr) import GHC.Generics (Generic) import Sound.RtMidi.Foreign@@ -85,6 +85,10 @@ defaultMessageSize :: Int defaultMessageSize = 4 +-- The max length of port names returned from 'rtmidi_get_port_name'+maxPortNameLength :: Int+maxPortNameLength = 256+ -- | Allows us to discriminate in/out functions in generic contexts data DeviceType = InputDeviceType@@ -205,12 +209,15 @@ -- 'Nothing' is returned if an invalid port specifier is provided. portName :: IsDevice d => d -> Int -> IO (Maybe String) portName d n = withDevicePtrUnguarded d $ \dptr -> do- x <- rtmidi_get_port_name dptr (toEnum n)- guardError dptr- s <- peekCString x- case s of- [] -> pure Nothing- _ -> pure (Just s)+ alloca $ \lenPtr -> do+ poke lenPtr (fromIntegral maxPortNameLength)+ allocaBytes maxPortNameLength $ \namePtr -> do+ used <- rtmidi_get_port_name dptr (toEnum n) namePtr lenPtr+ guardError dptr+ s <- peekCStringLen (namePtr, fromIntegral used)+ case s of+ [] -> pure Nothing+ _ -> pure (Just s) -- | Convenience function to list ports. --
Sound/RtMidi/Foreign.hsc view
@@ -101,7 +101,7 @@ rtmidi_get_port_count :: Ptr Wrapper -> IO CUInt foreign import ccall "rtmidi_c.h rtmidi_get_port_name"- rtmidi_get_port_name :: Ptr Wrapper -> CUInt -> IO CString+ rtmidi_get_port_name :: Ptr Wrapper -> CUInt -> CString -> Ptr CInt -> IO CInt foreign import ccall "rtmidi_c.h rtmidi_in_cancel_callback" rtmidi_in_cancel_callback :: Ptr Wrapper -> IO ()
rtmidi/RtMidi.cpp view
@@ -9,7 +9,7 @@ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes- Copyright (c) 2003-2019 Gary P. Scavone+ Copyright (c) 2003-2021 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files@@ -40,11 +40,31 @@ #include "RtMidi.h" #include <sstream> -#if defined(__MACOSX_CORE__)- #if TARGET_OS_IPHONE+#if (TARGET_OS_IPHONE == 1)+ #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos- #endif++ #include <mach/mach_time.h>+ class CTime2nsFactor+ {+ public:+ CTime2nsFactor()+ {+ mach_timebase_info_data_t tinfo;+ mach_timebase_info(&tinfo);+ Factor = (double)tinfo.numer / tinfo.denom;+ }+ static double Factor;+ };+ double CTime2nsFactor::Factor;+ static CTime2nsFactor InitTime2nsFactor;+ #undef AudioGetCurrentHostTime+ #undef AudioConvertHostTimeToNanos+ #define AudioGetCurrentHostTime (uint64_t) mach_absolute_time+ #define AudioConvertHostTimeToNanos(t) t *CTime2nsFactor::Factor+ #define EndianS32_BtoN(n) n+ #endif // Default for Windows is to add an identifier to the port names; this@@ -57,11 +77,12 @@ // // **************************************************************** // -#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__)+#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) #define __RTMIDI_DUMMY__ #endif #if defined(__MACOSX_CORE__)+#include <CoreMIDI/CoreMIDI.h> class MidiInCore: public MidiInApi {@@ -78,6 +99,7 @@ std::string getPortName( unsigned int portNumber ); protected:+ MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); void initialize( const std::string& clientName ); }; @@ -97,6 +119,7 @@ void sendMessage( const unsigned char *message, size_t size ); protected:+ MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); void initialize( const std::string& clientName ); }; @@ -231,6 +254,57 @@ #endif +#if defined(__WEB_MIDI_API__)++class MidiInWeb : public MidiInApi+{+ std::string client_name{};+ std::string web_midi_id{};+ int open_port_number{-1};++ public:+ MidiInWeb(const std::string &/*clientName*/, unsigned int queueSizeLimit );+ ~MidiInWeb( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );++ void onMidiMessage( uint8_t* data, double domHishResTimeStamp );++ protected:+ void initialize( const std::string& clientName );+};++class MidiOutWeb: public MidiOutApi+{+ std::string client_name{};+ std::string web_midi_id{};+ int open_port_number{-1};++ public:+ MidiOutWeb( const std::string &clientName );+ ~MidiOutWeb( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );+ void sendMessage( const unsigned char *message, size_t size );++ protected:+ void initialize( const std::string& clientName );+};++#endif+ #if defined(__RTMIDI_DUMMY__) class MidiInDummy: public MidiInApi@@ -285,6 +359,11 @@ rtapi_ = 0; } +RtMidi::RtMidi(RtMidi&& other) noexcept {+ rtapi_ = other.rtapi_;+ other.rtapi_ = nullptr;+}+ std::string RtMidi :: getVersion( void ) throw() { return std::string( RTMIDI_VERSION );@@ -300,6 +379,7 @@ { "jack" , "Jack" }, { "winmm" , "Windows MultiMedia" }, { "dummy" , "Dummy" },+ { "web" , "Web MIDI API" }, }; const unsigned int rtmidi_num_api_names = sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]);@@ -319,6 +399,9 @@ #if defined(__WINDOWS_MM__) RtMidi::WINDOWS_MM, #endif+#if defined(__WEB_MIDI_API__)+ RtMidi::WEB_MIDI_API,+#endif #if defined(__RTMIDI_DUMMY__) RtMidi::RTMIDI_DUMMY, #endif@@ -344,14 +427,14 @@ std::string RtMidi :: getApiName( RtMidi::Api api ) {- if (api < 0 || api >= RtMidi::NUM_APIS)+ if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) return ""; return rtmidi_api_names[api][0]; } std::string RtMidi :: getApiDisplayName( RtMidi::Api api ) {- if (api < 0 || api >= RtMidi::NUM_APIS)+ if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) return "Unknown"; return rtmidi_api_names[api][1]; }@@ -401,6 +484,10 @@ if ( api == MACOSX_CORE ) rtapi_ = new MidiInCore( clientName, queueSizeLimit ); #endif+#if defined(__WEB_MIDI_API__)+ if ( api == WEB_MIDI_API )+ rtapi_ = new MidiInWeb( clientName, queueSizeLimit );+#endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiInDummy( clientName, queueSizeLimit );@@ -469,6 +556,10 @@ if ( api == MACOSX_CORE ) rtapi_ = new MidiOutCore( clientName ); #endif+#if defined(__WEB_MIDI_API__)+ if ( api == WEB_MIDI_API )+ rtapi_ = new MidiOutWeb( clientName );+#endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiOutDummy( clientName );@@ -634,6 +725,12 @@ return timeStamp; } +void MidiInApi :: setBufferSize( unsigned int size, unsigned int count )+{+ inputData_.bufferSize = size;+ inputData_.bufferCount = count;+}+ unsigned int MidiInApi::MidiQueue::size( unsigned int *__back, unsigned int *__front ) {@@ -716,10 +813,11 @@ // MIDI input. We convert the system specific time stamps to delta // time values. -// OS-X CoreMIDI header files.-#include <CoreMIDI/CoreMIDI.h>-#include <CoreAudio/HostTime.h>-#include <CoreServices/CoreServices.h>+// These are not available on iOS.+#if (TARGET_OS_IPHONE == 0)+ #include <CoreAudio/HostTime.h>+ #include <CoreServices/CoreServices.h>+#endif // A structure to hold variables related to the CoreMIDI API // implementation.@@ -732,6 +830,20 @@ MIDISysexSendRequest sysexreq; }; +static MIDIClientRef CoreMidiClientSingleton = 0;++void RtMidi_setCoreMidiClientSingleton(MIDIClientRef client){+ CoreMidiClientSingleton = client;+}++void RtMidi_disposeCoreMidiClientSingleton(){+ if (CoreMidiClientSingleton == 0){+ return;+ }+ MIDIClientDispose( CoreMidiClientSingleton );+ CoreMidiClientSingleton = 0;+}+ //*********************************************************************// // API: OS-X // Class Definitions: MidiInCore@@ -899,24 +1011,37 @@ // Cleanup. CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- MIDIClientDispose( data->client ); if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); delete data; } +MIDIClientRef MidiInCore::getCoreMidiClientSingleton(const std::string& clientName) throw() {++ if (CoreMidiClientSingleton == 0){+ // Set up our client.+ MIDIClientRef client;++ CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );+ if ( result != noErr ) {+ std::ostringstream ost;+ ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";+ errorString_ = ost.str();+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return 0;+ }+ CFRelease( name );++ CoreMidiClientSingleton = client;+ }++ return CoreMidiClientSingleton;+}+ void MidiInCore :: initialize( const std::string& clientName ) { // Set up our client.- MIDIClientRef client;- CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );- OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );- if ( result != noErr ) {- std::ostringstream ost;- ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";- errorString_ = ost.str();- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }+ MIDIClientRef client = getCoreMidiClientSingleton(clientName); // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData;@@ -924,7 +1049,6 @@ data->endpoint = 0; apiData_ = (void *) data; inputData_.apiData = (void *) data;- CFRelease( name ); } void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName )@@ -960,7 +1084,6 @@ CFRelease( portNameRef ); if ( result != noErr ) {- MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return;@@ -970,7 +1093,6 @@ MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); if ( endpoint == 0 ) { MIDIPortDispose( port );- MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return;@@ -980,7 +1102,6 @@ result = MIDIPortConnectSource( port, endpoint, NULL ); if ( result != noErr ) { MIDIPortDispose( port );- MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return;@@ -1068,6 +1189,11 @@ CFRelease( str ); } + // some MIDI devices have a leading space in endpoint name. trim+ CFStringRef space = CFStringCreateWithCString(NULL, " ", kCFStringEncodingUTF8);+ CFStringTrim(result, space);+ CFRelease(space);+ MIDIEntityRef entity = 0; MIDIEndpointGetEntity( endpoint, &entity ); if ( entity == 0 )@@ -1223,31 +1349,43 @@ // Cleanup. CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- MIDIClientDispose( data->client ); if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); delete data; } +MIDIClientRef MidiOutCore::getCoreMidiClientSingleton(const std::string& clientName) throw() {++ if (CoreMidiClientSingleton == 0){+ // Set up our client.+ MIDIClientRef client;++ CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );+ if ( result != noErr ) {+ std::ostringstream ost;+ ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";+ errorString_ = ost.str();+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return 0;+ }+ CFRelease( name );++ CoreMidiClientSingleton = client;+ }++ return CoreMidiClientSingleton;+}+ void MidiOutCore :: initialize( const std::string& clientName ) { // Set up our client.- MIDIClientRef client;- CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );- OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );- if ( result != noErr ) {- std::ostringstream ost;- ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";- errorString_ = ost.str();- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }+ MIDIClientRef client = getCoreMidiClientSingleton(clientName); // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; data->client = client; data->endpoint = 0; apiData_ = (void *) data;- CFRelease( name ); } unsigned int MidiOutCore :: getPortCount()@@ -1310,7 +1448,6 @@ OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); CFRelease( portNameRef ); if ( result != noErr ) {- MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return;@@ -1320,7 +1457,6 @@ MIDIEndpointRef destination = MIDIGetDestination( portNumber ); if ( destination == 0 ) { MIDIPortDispose( port );- MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return;@@ -1402,50 +1538,54 @@ return; } - MIDITimeStamp timeStamp = AudioGetCurrentHostTime();- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- OSStatus result;- if ( message[0] != 0xF0 && nBytes > 3 ) { errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; error( RtMidiError::WARNING, errorString_ ); return; } - Byte buffer[nBytes+(sizeof( MIDIPacketList ))];+ MIDITimeStamp timeStamp = AudioGetCurrentHostTime();+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);+ OSStatus result;++ ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;+ Byte buffer[bufsize+16]; // pad for other struct members ByteCount listSize = sizeof( buffer ); MIDIPacketList *packetList = (MIDIPacketList*)buffer;- MIDIPacket *packet = MIDIPacketListInit( packetList ); ByteCount remainingBytes = nBytes;- while ( remainingBytes && packet ) {- ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket+ while ( remainingBytes ) {+ MIDIPacket *packet = MIDIPacketListInit( packetList );+ // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer,+ // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one+ // MIDIPacket. Here, we reuse the memory allocated above on the stack for all.+ ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes]; packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr ); remainingBytes -= bytesForPacket;- } - if ( !packet ) {- errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }+ if ( !packet ) {+ errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ } - // Send to any destinations that may have connected to us.- if ( data->endpoint ) {- result = MIDIReceived( data->endpoint, packetList );- if ( result != noErr ) {- errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";- error( RtMidiError::WARNING, errorString_ );+ // Send to any destinations that may have connected to us.+ if ( data->endpoint ) {+ result = MIDIReceived( data->endpoint, packetList );+ if ( result != noErr ) {+ errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";+ error( RtMidiError::WARNING, errorString_ );+ } }- } - // And send to an explicit destination port if we're connected.- if ( connected_ ) {- result = MIDISend( data->port, data->destinationId, packetList );- if ( result != noErr ) {- errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";- error( RtMidiError::WARNING, errorString_ );+ // And send to an explicit destination port if we're connected.+ if ( connected_ ) {+ result = MIDISend( data->port, data->destinationId, packetList );+ if ( result != noErr ) {+ errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";+ error( RtMidiError::WARNING, errorString_ );+ } } } }@@ -1487,6 +1627,7 @@ snd_seq_port_subscribe_t *subscription; snd_midi_event_t *coder; unsigned int bufferSize;+ unsigned int requestedBufferSize; unsigned char *buffer; pthread_t thread; pthread_t dummy_thread_id;@@ -1517,7 +1658,6 @@ snd_seq_event_t *ev; int result;- apiData->bufferSize = 32; result = snd_midi_event_new( 0, &apiData->coder ); if ( result < 0 ) { data->doInput = false;@@ -1769,6 +1909,7 @@ data->thread = data->dummy_thread_id; data->trigger_fds[0] = -1; data->trigger_fds[1] = -1;+ data->bufferSize = inputData_.bufferSize; apiData_ = (void *) data; inputData_.apiData = (void *) data; @@ -2293,7 +2434,7 @@ void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) {- int result;+ long result; AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); unsigned int nBytes = static_cast<unsigned int> (size); if ( nBytes > data->bufferSize ) {@@ -2313,25 +2454,38 @@ } } - snd_seq_event_t ev;- snd_seq_ev_clear( &ev );- snd_seq_ev_set_source( &ev, data->vport );- snd_seq_ev_set_subs( &ev );- snd_seq_ev_set_direct( &ev ); for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message[i];- result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );- if ( result < (int)nBytes ) {- errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";- error( RtMidiError::WARNING, errorString_ );- return;- } - // Send the event.- result = snd_seq_event_output( data->seq, &ev );- if ( result < 0 ) {- errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";- error( RtMidiError::WARNING, errorString_ );- return;+ unsigned int offset = 0;+ while (offset < nBytes) {+ snd_seq_event_t ev;+ snd_seq_ev_clear( &ev );+ snd_seq_ev_set_source( &ev, data->vport );+ snd_seq_ev_set_subs( &ev );+ snd_seq_ev_set_direct( &ev );+ result = snd_midi_event_encode( data->coder, data->buffer + offset,+ (long)(nBytes - offset), &ev );+ if ( result < 0 ) {+ errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ if ( ev.type == SND_SEQ_EVENT_NONE ) {+ errorString_ = "MidiOutAlsa::sendMessage: incomplete message!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ offset += result;++ // Send the event.+ result = snd_seq_event_output( data->seq, &ev );+ if ( result < 0 ) {+ errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";+ error( RtMidiError::WARNING, errorString_ );+ return;+ } } snd_seq_drain_output( data->seq ); }@@ -2386,9 +2540,6 @@ return u8str; } -#define RT_SYSEX_BUFFER_SIZE 1024-#define RT_SYSEX_BUFFER_COUNT 4- // A structure to hold variables related to the CoreMIDI API // implementation. struct WinMidiData {@@ -2396,7 +2547,7 @@ HMIDIOUT outHandle; // Handle to Midi Output Device DWORD lastTime; MidiInApi::MidiMessage message;- LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];+ std::vector<LPMIDIHDR> sysexBuffer; CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo }; @@ -2576,10 +2727,11 @@ } // Allocate and init the sysex buffers.- for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {+ data->sysexBuffer.resize( inputData_.bufferCount );+ for ( int i=0; i < inputData_.bufferCount; ++i ) { data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];- data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];- data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE;+ data->sysexBuffer[i]->lpData = new char[ inputData_.bufferSize ];+ data->sysexBuffer[i]->dwBufferLength = inputData_.bufferSize; data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator data->sysexBuffer[i]->dwFlags = 0; @@ -2630,7 +2782,7 @@ midiInReset( data->inHandle ); midiInStop( data->inHandle ); - for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {+ for ( int i=0; i < data->sysexBuffer.size(); ++i ) { int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); delete [] data->sysexBuffer[i]->lpData; delete [] data->sysexBuffer[i];@@ -2811,7 +2963,10 @@ { if ( connected_ ) { WinMidiData *data = static_cast<WinMidiData *> (apiData_);- midiOutReset( data->outHandle );+ // Disabled because midiOutReset triggers 0x7b (if any note was ON) and 0x79 "Reset All+ // Controllers" (to all 16 channels) CC messages which is undesirable (see issue #222)+ // midiOutReset( data->outHandle );+ midiOutClose( data->outHandle ); data->outHandle = 0; connected_ = false;@@ -2936,6 +3091,7 @@ #include <jack/jack.h> #include <jack/midiport.h> #include <jack/ringbuffer.h>+#include <pthread.h> #ifdef HAVE_SEMAPHORE #include <semaphore.h> #endif@@ -2945,8 +3101,8 @@ struct JackMidiData { jack_client_t *client; jack_port_t *port;- jack_ringbuffer_t *buffSize;- jack_ringbuffer_t *buffMessage;+ jack_ringbuffer_t *buff;+ int buffMaxWrite; // actual writable size, usually 1 less than ringbuffer jack_time_t lastTime; #ifdef HAVE_SEMAPHORE sem_t sem_cleanup;@@ -3101,6 +3257,8 @@ if ( data->port == NULL ) { errorString_ = "MidiInJack::openPort: JACK error creating port";+ if (portName.size() >= (size_t)jack_port_name_size())+ errorString_ += " (port name too long?)"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; }@@ -3123,6 +3281,8 @@ if ( data->port == NULL ) { errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";+ if (portName.size() >= (size_t)jack_port_name_size())+ errorString_ += " (port name too long?)"; error( RtMidiError::DRIVER_ERROR, errorString_ ); } }@@ -3226,11 +3386,15 @@ void *buff = jack_port_get_buffer( data->port, nframes ); jack_midi_clear_buffer( buff ); - while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) {- jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof( space ) );- midiData = jack_midi_event_reserve( buff, 0, space );+ while ( jack_ringbuffer_peek( data->buff, (char *) &space, sizeof( space ) ) == sizeof(space) &&+ jack_ringbuffer_read_space( data->buff ) >= sizeof(space) + space ) {+ jack_ringbuffer_read_advance( data->buff, sizeof(space) ); - jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space );+ midiData = jack_midi_event_reserve( buff, 0, space );+ if ( midiData )+ jack_ringbuffer_read( data->buff, (char *) midiData, (size_t) space );+ else+ jack_ringbuffer_read_advance( data->buff, (size_t) space ); } #ifdef HAVE_SEMAPHORE@@ -3269,8 +3433,8 @@ return; // Initialize output ringbuffers- data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );- data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );+ data->buff = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );+ data->buffMaxWrite = (int) jack_ringbuffer_write_space( data->buff ); // Initialize JACK client if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) {@@ -3289,8 +3453,7 @@ MidiOutJack::closePort(); // Cleanup- jack_ringbuffer_free( data->buffSize );- jack_ringbuffer_free( data->buffMessage );+ jack_ringbuffer_free( data->buff ); if ( data->client ) { jack_client_close( data->client ); }@@ -3316,6 +3479,8 @@ if ( data->port == NULL ) { errorString_ = "MidiOutJack::openPort: JACK error creating port";+ if (portName.size() >= (size_t)jack_port_name_size())+ errorString_ += " (port name too long?)"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; }@@ -3338,6 +3503,8 @@ if ( data->port == NULL ) { errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";+ if (portName.size() >= (size_t)jack_port_name_size())+ errorString_ += " (port name too long?)"; error( RtMidiError::DRIVER_ERROR, errorString_ ); } }@@ -3437,9 +3604,331 @@ int nBytes = static_cast<int>(size); JackMidiData *data = static_cast<JackMidiData *> (apiData_); + if ( size + sizeof(nBytes) > (size_t) data->buffMaxWrite )+ return;++ while ( jack_ringbuffer_write_space(data->buff) < sizeof(nBytes) + size )+ pthread_yield();+ // Write full message to buffer- jack_ringbuffer_write( data->buffMessage, ( const char * ) message, nBytes );- jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) );+ jack_ringbuffer_write( data->buff, ( char * ) &nBytes, sizeof( nBytes ) );+ jack_ringbuffer_write( data->buff, ( const char * ) message, nBytes ); } #endif // __UNIX_JACK__++//*********************************************************************//+// API: Web MIDI+//+// Written primarily by Atsushi Eno, February 2020.+//+// *********************************************************************//++#if defined(__WEB_MIDI_API__)++#include <emscripten.h>++//*********************************************************************//+// API: WEB MIDI+// Class Definitions: WebMidiAccessShim+//*********************************************************************//++class WebMidiAccessShim+{+public:+ WebMidiAccessShim();+ ~WebMidiAccessShim();+ std::string getPortName( unsigned int portNumber, bool isInput );+};++std::unique_ptr<WebMidiAccessShim> shim{nullptr};++void ensureShim()+{+ if ( shim.get() != nullptr )+ return;+ shim.reset( new WebMidiAccessShim() );+}++bool checkWebMidiAvailability()+{+ ensureShim();++ return MAIN_THREAD_EM_ASM_INT( {+ if ( typeof window._rtmidi_internals_waiting === "undefined" ) {+ console.log ( "Attempted to use Web MIDI API without trying to open it." );+ return false;+ }+ if ( window._rtmidi_internals_waiting ) {+ console.log ( "Attempted to use Web MIDI API while it is being queried." );+ return false;+ }+ if ( _rtmidi_internals_midi_access == null ) {+ console.log ( "Attempted to use Web MIDI API while it already turned out to be unavailable." );+ return false;+ }+ return true;+ } );+}++WebMidiAccessShim::WebMidiAccessShim()+{+ MAIN_THREAD_ASYNC_EM_ASM( {+ if( typeof window._rtmidi_internals_midi_access !== "undefined" )+ return;+ if( typeof window._rtmidi_internals_waiting !== "undefined" ) {+ console.log( "MIDI Access was requested while another request is in progress." );+ return;+ }++ // define functions+ window._rtmidi_internals_get_port_by_number = function( portNumber, isInput ) {+ var midi = window._rtmidi_internals_midi_access;+ var devices = isInput ? midi.inputs : midi.outputs;+ var i = 0;+ for (var device of devices.values()) {+ if ( i == portNumber )+ return device;+ i++;+ }+ console.log( "MIDI " + (isInput ? "input" : "output") + " device of portNumber " + portNumber + " is not found.");+ return null;+ };++ window._rtmidi_internals_waiting = true;+ window.navigator.requestMIDIAccess( {"sysex": true} ).then( (midiAccess) => {+ window._rtmidi_internals_midi_access = midiAccess;+ window._rtmidi_internals_latest_message_timestamp = 0.0;+ window._rtmidi_internals_waiting = false;+ if( midiAccess == null ) {+ console.log ( "Could not get access to MIDI API" );+ }+ } );+ } );+}++WebMidiAccessShim::~WebMidiAccessShim()+{+}++std::string WebMidiAccessShim::getPortName( unsigned int portNumber, bool isInput )+{+ if( !checkWebMidiAvailability() )+ return "";+ char *ret = (char*) MAIN_THREAD_EM_ASM_INT( {+ var port = window._rtmidi_internals_get_port_by_number($0, $1);+ if( port == null)+ return null;+ var length = lengthBytesUTF8(port.name) + 1;+ var ret = _malloc(length);+ stringToUTF8(port.name, ret, length);+ return ret;+ }, portNumber, isInput, &ret );+ if (ret == nullptr)+ return "";+ std::string s = ret;+ free(ret);+ return s;+}++//*********************************************************************//+// API: WEB MIDI+// Class Definitions: MidiInWeb+//*********************************************************************//++MidiInWeb::MidiInWeb( const std::string &clientName, unsigned int queueSizeLimit )+ : MidiInApi( queueSizeLimit )+{+ initialize( clientName );+}++MidiInWeb::~MidiInWeb( void )+{+ closePort();+}++extern "C" void EMSCRIPTEN_KEEPALIVE rtmidi_onMidiMessageProc( MidiInApi::RtMidiInData* data, uint8_t* inputBytes, int32_t length, double domHighResTimeStamp )+{+ auto &message = data->message;+ message.bytes.resize(message.bytes.size() + length);+ memcpy(message.bytes.data(), inputBytes, length);+ // FIXME: handle timestamp+ if ( data->usingCallback ) {+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;+ callback( message.timeStamp, &message.bytes, data->userData );+ }+}++void MidiInWeb::openPort( unsigned int portNumber, const std::string &portName )+{+ if( !checkWebMidiAvailability() )+ return;+ if (open_port_number >= 0)+ return;++ MAIN_THREAD_EM_ASM( {+ // In Web MIDI API world, there is no step to open a port, but we have to register the input callback instead.+ var input = window._rtmidi_internals_get_port_by_number($0, true);+ input.onmidimessage = function(e) {+ // In RtMidi world, timestamps are delta time from previous message, while in Web MIDI world+ // timestamps are relative to window creation time (i.e. kind of absolute time with window "epoch" time).+ var rtmidiTimestamp = window._rtmidi_internals_latest_message_timestamp == 0.0 ? 0.0 : e.timeStamp - window._rtmidi_internals_latest_message_timestamp;+ window._rtmidi_internals_latest_message_timestamp = e.timeStamp;+ Module.ccall( 'rtmidi_onMidiMessageProc', 'void', ['number', 'array', 'number', 'number'], [$1, e.data, e.data.length, rtmidiTimestamp] );+ };+ }, portNumber, &inputData_ );+ open_port_number = portNumber;+}++void MidiInWeb::openVirtualPort( const std::string &portName )+{++ errorString_ = "MidiInWeb::openVirtualPort: this function is not implemented for the Web MIDI API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiInWeb::closePort( void )+{+ if( open_port_number < 0 )+ return;++ MAIN_THREAD_EM_ASM( {+ var input = _rtmidi_internals_get_port_by_number($0, true);+ if( input == null ) {+ console.log( "Port #" + $0 + " could not be found.");+ return;+ }+ // unregister event handler+ input.onmidimessage = null;+ }, open_port_number );+ open_port_number = -1;+}++void MidiInWeb::setClientName( const std::string &clientName )+{+ client_name = clientName;+}++void MidiInWeb::setPortName( const std::string &portName )+{++ errorString_ = "MidiInWeb::setPortName: this function is not implemented for the Web MIDI API!";+ error( RtMidiError::WARNING, errorString_ );++}++unsigned int MidiInWeb::getPortCount( void )+{+ if( !checkWebMidiAvailability() )+ return 0;+ return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.inputs.size; } );+}++std::string MidiInWeb::getPortName( unsigned int portNumber )+{+ if( !checkWebMidiAvailability() )+ return "";+ return shim->getPortName( portNumber, true );+}++void MidiInWeb::initialize( const std::string& clientName )+{+ ensureShim();+ setClientName( clientName );+}++//*********************************************************************//+// API: WEB MIDI+// Class Definitions: MidiOutWeb+//*********************************************************************//++MidiOutWeb::MidiOutWeb( const std::string &clientName )+{+ initialize( clientName );+}++MidiOutWeb::~MidiOutWeb( void )+{+ closePort();+}++void MidiOutWeb::openPort( unsigned int portNumber, const std::string &portName )+{+ if( !checkWebMidiAvailability() )+ return;+ if (open_port_number >= 0)+ return;+ // In Web MIDI API world, there is no step to open a port.++ open_port_number = portNumber;+}++void MidiOutWeb::openVirtualPort( const std::string &portName )+{++ errorString_ = "MidiOutWeb::openVirtualPort: this function is not implemented for the Web MIDI API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiOutWeb::closePort( void )+{+ // there is really nothing to do for output at JS side.+ open_port_number = -1;+}++void MidiOutWeb::setClientName( const std::string &clientName )+{+ client_name = clientName;+}++void MidiOutWeb::setPortName( const std::string &portName )+{++ errorString_ = "MidiOutWeb::setPortName: this function is not implemented for the Web MIDI API!";+ error( RtMidiError::WARNING, errorString_ );++}++unsigned int MidiOutWeb::getPortCount( void )+{+ if( !checkWebMidiAvailability() )+ return 0;+ return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.outputs.size; } );+}++std::string MidiOutWeb::getPortName( unsigned int portNumber )+{+ if( !checkWebMidiAvailability() )+ return "";+ return shim->getPortName( portNumber, false );+}++void MidiOutWeb::sendMessage( const unsigned char *message, size_t size )+{+ if( open_port_number < 0 )+ return;++ MAIN_THREAD_EM_ASM( {+ var output = _rtmidi_internals_get_port_by_number( $0, false );+ if( output == null ) {+ console.log( "Port #" + $0 + " could not be found.");+ return;+ }+ var buf = new ArrayBuffer ($2);+ var msg = new Uint8Array( buf );+ msg.set( new Uint8Array( Module.HEAPU8.buffer.slice( $1, $1 + $2 ) ) );+ output.send( msg );+ }, open_port_number, message, size );+}++void MidiOutWeb::initialize( const std::string& clientName )+{+ if ( shim.get() != nullptr )+ return;+ shim.reset( new WebMidiAccessShim() );+ setClientName( clientName );+}++#endif // __WEB_MIDI_API__
rtmidi/RtMidi.h view
@@ -9,7 +9,7 @@ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes- Copyright (c) 2003-2019 Gary P. Scavone+ Copyright (c) 2003-2021 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files@@ -58,13 +58,14 @@ #endif #endif -#define RTMIDI_VERSION "4.0.0"+#define RTMIDI_VERSION "5.0.0" #include <exception> #include <iostream> #include <string> #include <vector> + /************************************************************************/ /*! \class RtMidiError \brief Exception handling class for RtMidi.@@ -132,6 +133,8 @@ class RTMIDI_DLL_PUBLIC RtMidi { public:++ RtMidi(RtMidi&& other) noexcept; //! MIDI API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */@@ -140,6 +143,7 @@ UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ RTMIDI_DUMMY, /*!< A compilable but non-functional API. */+ WEB_MIDI_API, /*!< W3C Web MIDI API. */ NUM_APIS /*!< Number of values in this enum. */ }; @@ -213,6 +217,10 @@ RtMidi(); virtual ~RtMidi(); MidiApi *rtapi_;++ /* Make the class non-copyable */+ RtMidi(RtMidi& other) = delete;+ RtMidi& operator=(RtMidi& other) = delete; }; /**********************************************************************/@@ -228,8 +236,6 @@ 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-2017. */ /**********************************************************************/ @@ -250,7 +256,6 @@ class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi { public:- //! User callback function type definition. typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData ); @@ -276,6 +281,8 @@ const std::string& clientName = "RtMidi Input Client", unsigned int queueSizeLimit = 100 ); + RtMidiIn(RtMidiIn&& other) noexcept : RtMidi(std::move(other)) { }+ //! If a MIDI connection is still open, it will be closed by the destructor. ~RtMidiIn ( void ) throw(); @@ -373,6 +380,19 @@ */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); + //! Set maximum expected incoming message size.+ /*!+ For APIs that require manual buffer management, it can be useful to set the buffer+ size and buffer count when expecting to receive large SysEx messages. Note that+ currently this function has no effect when called after openPort(). The default+ buffer size is 1024 with a count of 4 buffers, which should be sufficient for most+ cases; as mentioned, this does not affect all API backends, since most either support+ dynamically scalable buffers or take care of buffer handling themselves. It is+ principally intended for users of the Windows MM backend who must support receiving+ especially large messages.+ */+ virtual void setBufferSize( unsigned int size, unsigned int count );+ protected: void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ); };@@ -388,8 +408,6 @@ 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-2017. */ /**********************************************************************/ @@ -407,6 +425,8 @@ RtMidiOut( RtMidi::Api api=UNSPECIFIED, const std::string& clientName = "RtMidi Output Client" ); + RtMidiOut(RtMidiOut&& other) noexcept : RtMidi(std::move(other)) { }+ //! The destructor closes any open MIDI connections. ~RtMidiOut( void ) throw(); @@ -527,6 +547,7 @@ RtMidiErrorCallback errorCallback_; bool firstErrorOccurred_; void *errorCallbackUserData_;+ }; class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi@@ -539,6 +560,7 @@ void cancelCallback( void ); virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); double getMessage( std::vector<unsigned char> *message );+ virtual void setBufferSize( unsigned int size, unsigned int count ); // A MIDI structure used internally by the class to store incoming // messages. Each message represents one and only one MIDI message.@@ -580,11 +602,13 @@ RtMidiIn::RtMidiCallback userCallback; void *userData; bool continueSysex;+ unsigned int bufferSize;+ unsigned int bufferCount; // Default constructor. RtMidiInData() : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false),- userCallback(0), userData(0), continueSysex(false) {}+ userCallback(0), userData(0), continueSysex(false), bufferSize(1024), bufferCount(4) {} }; protected:@@ -618,6 +642,7 @@ inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { static_cast<MidiInApi *>(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return static_cast<MidiInApi *>(rtapi_)->getMessage( message ); } inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }+inline void RtMidiIn :: setBufferSize( unsigned int size, unsigned int count ) { static_cast<MidiInApi *>(rtapi_)->setBufferSize(size, count); } inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); }
rtmidi/rtmidi_c.cpp view
@@ -17,6 +17,7 @@ ENUM_EQUAL( RTMIDI_API_UNIX_JACK, RtMidi::UNIX_JACK ); ENUM_EQUAL( RTMIDI_API_WINDOWS_MM, RtMidi::WINDOWS_MM ); ENUM_EQUAL( RTMIDI_API_RTMIDI_DUMMY, RtMidi::RTMIDI_DUMMY );+ ENUM_EQUAL( RTMIDI_API_WEB_MIDI_API, RtMidi::WEB_MIDI_API ); ENUM_EQUAL( RTMIDI_ERROR_WARNING, RtMidiError::WARNING ); ENUM_EQUAL( RTMIDI_ERROR_DEBUG_WARNING, RtMidiError::DEBUG_WARNING );@@ -34,12 +35,12 @@ class CallbackProxyUserData { public:- CallbackProxyUserData (RtMidiCCallback cCallback, void *userData)- : c_callback (cCallback), user_data (userData)- {- }- RtMidiCCallback c_callback;- void *user_data;+ CallbackProxyUserData (RtMidiCCallback cCallback, void *userData)+ : c_callback (cCallback), user_data (userData)+ {+ }+ RtMidiCCallback c_callback;+ void *user_data; }; extern "C" const enum RtMidiApi rtmidi_compiled_apis[]; // casting from RtMidi::Api[]@@ -80,8 +81,8 @@ void rtmidi_error (MidiApi *api, enum RtMidiErrorType type, const char* errorString) {- std::string msg = errorString;- api->error ((RtMidiError::Type) type, msg);+ std::string msg = errorString;+ api->error ((RtMidiError::Type) type, msg); } void rtmidi_open_port (RtMidiPtr device, unsigned int portNumber, const char *portName)@@ -89,7 +90,7 @@ std::string name = portName; try { ((RtMidi*) device->ptr)->openPort (portNumber, name);- + } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();@@ -101,7 +102,7 @@ std::string name = portName; try { ((RtMidi*) device->ptr)->openVirtualPort (name);- + } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();@@ -111,7 +112,7 @@ void rtmidi_close_port (RtMidiPtr device) {- try { + try { ((RtMidi*) device->ptr)->closePort (); } catch (const RtMidiError & err) {@@ -132,32 +133,42 @@ } } -const char* rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber)+int rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber, char * bufOut, int * bufLen) {+ if (bufOut == nullptr && bufLen == nullptr) {+ return -1;+ }++ std::string name; try {- std::string name = ((RtMidi*) device->ptr)->getPortName (portNumber);- return strdup (name.c_str ());- + name = ((RtMidi*) device->ptr)->getPortName (portNumber); } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();- return "";+ return -1; }++ if (bufOut == nullptr) {+ *bufLen = static_cast<int>(name.size()) + 1;+ return 0;+ }++ return snprintf(bufOut, static_cast<size_t>(*bufLen), "%s", name.c_str()); } /* RtMidiIn API */ RtMidiInPtr rtmidi_in_create_default () { RtMidiWrapper* wrp = new RtMidiWrapper;- + try { RtMidiIn* rIn = new RtMidiIn ();- + wrp->ptr = (void*) rIn; wrp->data = 0; wrp->ok = true; wrp->msg = "";- + } catch (const RtMidiError & err) { wrp->ptr = 0; wrp->data = 0;@@ -172,10 +183,10 @@ { std::string name = clientName; RtMidiWrapper* wrp = new RtMidiWrapper;- + try { RtMidiIn* rIn = new RtMidiIn ((RtMidi::Api) api, name, queueSizeLimit);- + wrp->ptr = (void*) rIn; wrp->data = 0; wrp->ok = true;@@ -203,7 +214,7 @@ { try { return (RtMidiApi) ((RtMidiIn*) device->ptr)->getCurrentApi ();- + } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();@@ -215,8 +226,8 @@ static void callback_proxy (double timeStamp, std::vector<unsigned char> *message, void *userData) {- CallbackProxyUserData* data = reinterpret_cast<CallbackProxyUserData*> (userData);- data->c_callback (timeStamp, message->data (), message->size (), data->user_data);+ CallbackProxyUserData* data = reinterpret_cast<CallbackProxyUserData*> (userData);+ data->c_callback (timeStamp, message->data (), message->size (), data->user_data); } void rtmidi_in_set_callback (RtMidiInPtr device, RtMidiCCallback callback, void *userData)@@ -246,10 +257,10 @@ void rtmidi_in_ignore_types (RtMidiInPtr device, bool midiSysex, bool midiTime, bool midiSense) {- ((RtMidiIn*) device->ptr)->ignoreTypes (midiSysex, midiTime, midiSense);+ ((RtMidiIn*) device->ptr)->ignoreTypes (midiSysex, midiTime, midiSense); } -double rtmidi_in_get_message (RtMidiInPtr device, +double rtmidi_in_get_message (RtMidiInPtr device, unsigned char *message, size_t *size) {@@ -264,7 +275,7 @@ *size = v.size(); return ret;- } + } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();@@ -284,12 +295,12 @@ try { RtMidiOut* rOut = new RtMidiOut ();- + wrp->ptr = (void*) rOut; wrp->data = 0; wrp->ok = true; wrp->msg = "";- + } catch (const RtMidiError & err) { wrp->ptr = 0; wrp->data = 0;@@ -307,12 +318,12 @@ try { RtMidiOut* rOut = new RtMidiOut ((RtMidi::Api) api, name);- + wrp->ptr = (void*) rOut; wrp->data = 0; wrp->ok = true; wrp->msg = "";- + } catch (const RtMidiError & err) { wrp->ptr = 0; wrp->data = 0;
rtmidi/rtmidi_c.h view
@@ -41,7 +41,7 @@ void* ptr; void* data; - //! True when the last function call was OK. + //! True when the last function call was OK. bool ok; //! If an error occured (ok != true), set to an error message.@@ -65,6 +65,7 @@ RTMIDI_API_UNIX_JACK, /*!< The Jack Low-Latency MIDI Server API. */ RTMIDI_API_WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ RTMIDI_API_RTMIDI_DUMMY, /*!< A compilable but non-functional API. */+ RTMIDI_API_WEB_MIDI_API, /*!< W3C Web MIDI API. */ RTMIDI_API_NUM /*!< Number of values in this enum. */ }; @@ -136,8 +137,8 @@ */ RTMIDIAPI void rtmidi_open_port (RtMidiPtr device, unsigned int portNumber, const char *portName); -/*! \brief Creates a virtual MIDI port to which other software applications can - * connect. +/*! \brief Creates a virtual MIDI port to which other software applications can+ * connect. * * \param portName Name for the application port. *@@ -155,10 +156,14 @@ */ RTMIDIAPI unsigned int rtmidi_get_port_count (RtMidiPtr device); -/*! \brief Return a string identifier for the specified MIDI input port number.+/*! \brief Access a string identifier for the specified MIDI input port number.+ *+ * To prevent memory leaks a char buffer must be passed to this function.+ * NULL can be passed as bufOut parameter, and that will write the required buffer length in the bufLen.+ * * See RtMidi::getPortName(). */-RTMIDIAPI const char* rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber);+RTMIDIAPI int rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber, char * bufOut, int * bufLen); /* RtMidiIn API */ @@ -203,8 +208,9 @@ * \param message Must point to a char* that is already allocated. * SYSEX messages maximum size being 1024, a statically * allocated array could- * be sufficient. - * \param size Is used to return the size of the message obtained. + * be sufficient.+ * \param size Is used to return the size of the message obtained.+ * Must be set to the size of \ref message when calling. * * See RtMidiIn::getMessage(). */