packages feed

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 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().  */