coreaudio: per-port parser for incoming MIDI, copied from ALSA Raw MIDI support
authorPaul Davis <paul@linuxaudiosystems.com>
Thu, 7 Jan 2016 21:42:24 +0000 (16:42 -0500)
committerPaul Davis <paul@linuxaudiosystems.com>
Thu, 7 Jan 2016 21:42:34 +0000 (16:42 -0500)
libs/backends/coreaudio/coreaudio_backend.cc
libs/backends/coreaudio/coreaudio_backend.h

index c08dacd8dd1fdc3206374aed31f40cefa3f12604..beeeb2e86752329178805315b667b95d9377a422 100644 (file)
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-
-/* use an additional midi message parser
- *
- * coreaudio does packetize midi. every packet includes a timestamp.
- * With any real midi-device with a phyical layer
- * 1 packet = 1 event (no concurrent events are possible on a cable)
- *
- * Howver, some USB-midi keyboards manage to send concurrent events
- * which end up in the same packet (eg. 6 byte message: 2 note-on).
- *
- * An additional parser is needed to separate them
- */
-#define USE_MIDI_PARSER
-
-
 #include <regex.h>
 #include <sys/mman.h>
 #include <sys/time.h>
@@ -119,13 +104,6 @@ CoreAudioBackend::CoreAudioBackend (AudioEngine& e, AudioBackendInfo& info)
        , _dsp_load (0)
        , _processed_samples (0)
        , _port_change_flag (false)
-#ifdef USE_MIDI_PARSER
-       , _unbuffered_bytes(0)
-       , _total_bytes(0)
-       , _expected_bytes(0)
-       , _status_byte(0)
-       , _parser_bytes(0)
-#endif
 {
        _instance_name = s_instance_name;
        pthread_mutex_init (&_port_callback_mutex, 0);
@@ -1365,6 +1343,7 @@ CoreAudioBackend::midi_event_get (
        return 0;
 }
 
+
 int
 CoreAudioBackend::midi_event_put (
                void* port_buffer,
@@ -1384,6 +1363,7 @@ CoreAudioBackend::midi_event_put (
        return 0;
 }
 
+
 uint32_t
 CoreAudioBackend::get_midi_event_count (void* port_buffer)
 {
@@ -1674,101 +1654,6 @@ CoreAudioBackend::freewheel_thread ()
        }
        return 0;
 }
-
-#ifdef USE_MIDI_PARSER
-bool
-CoreAudioBackend::midi_process_byte (const uint8_t byte)
-{
-       if (byte >= 0xf8) {
-               // Realtime
-               if (byte == 0xfd) {
-                       // undefined
-                       return false;
-               }
-               midi_prepare_byte_event (byte);
-               return true;
-       }
-       if (byte == 0xf7) {
-               // Sysex end
-               if (_status_byte == 0xf0) {
-                       midi_record_byte (byte);
-                       return midi_prepare_buffered_event ();
-               }
-               _total_bytes = 0;
-               _unbuffered_bytes = 0;
-               _expected_bytes = 0;
-               _status_byte = 0;
-               return false;
-       }
-       if (byte >= 0x80) {
-               // Non-realtime status byte
-               if (_total_bytes) {
-                       _total_bytes = 0;
-                       _unbuffered_bytes = 0;
-               }
-               _status_byte = byte;
-               switch (byte & 0xf0) {
-                       case 0x80:
-                       case 0x90:
-                       case 0xa0:
-                       case 0xb0:
-                       case 0xe0:
-                               // Note On, Note Off, Aftertouch, Control Change, Pitch Wheel
-                               _expected_bytes = 3;
-                               break;
-                       case 0xc0:
-                       case 0xd0:
-                               // Program Change, Channel Pressure
-                               _expected_bytes = 2;
-                               break;
-                       case 0xf0:
-                               switch (byte) {
-                                       case 0xf0:
-                                               // Sysex
-                                               _expected_bytes = 0;
-                                               break;
-                                       case 0xf1:
-                                       case 0xf3:
-                                               // MTC Quarter Frame, Song Select
-                                               _expected_bytes = 2;
-                                               break;
-                                       case 0xf2:
-                                               // Song Position
-                                               _expected_bytes = 3;
-                                               break;
-                                       case 0xf4:
-                                       case 0xf5:
-                                               // Undefined
-                                               _expected_bytes = 0;
-                                               _status_byte = 0;
-                                               return false;
-                                       case 0xf6:
-                                               // Tune Request
-                                               midi_prepare_byte_event (byte);
-                                               _expected_bytes = 0;
-                                               _status_byte = 0;
-                                               return true;
-                               }
-               }
-               midi_record_byte (byte);
-               return false;
-       }
-       // Data byte
-       if (! _status_byte) {
-               // Data bytes without a status will be discarded.
-               _total_bytes++;
-               _unbuffered_bytes++;
-               return false;
-       }
-       if (! _total_bytes) {
-               midi_record_byte (_status_byte);
-       }
-       midi_record_byte(byte);
-       return (_total_bytes == _expected_bytes) ? midi_prepare_buffered_event() : false;
-}
-#endif
-
-
 int
 CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t host_time)
 {
@@ -1816,39 +1701,20 @@ CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t hos
        /* get midi */
        i=0;
        for (std::vector<CoreBackendPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
-               CoreMidiBuffer* mbuf = static_cast<CoreMidiBuffer*>((*it)->get_buffer(0));
-               mbuf->clear();
-               uint64_t time_ns;
-               uint8_t data[128]; // matches CoreMidi's MIDIPacket
-               size_t size = sizeof(data);
-               while (_midiio->recv_event (i, nominal_time, time_ns, data, size)) {
-                       pframes_t time = floor((float) time_ns * _samplerate * 1e-9);
-                       assert (time < n_samples);
-#ifndef USE_MIDI_PARSER
-                       midi_event_put((void*)mbuf, time, data, size);
-#else
-                       assert (size < 128);// coremidi limit per packet
-                       bool first_time = true; // this would need to be rememberd per port.
-                       for (size_t mb = 0; mb < size; ++mb) {
-                               if (first_time && !(data[mb] & 0x80)) {
-                                       /* expect a status byte at the beginning or every Packet.
-                                        *
-                                        * This parser drops messages spanning multiple packets
-                                        * (sysex > 127 bytes).
-                                        * see also libs/backends/alsa/alsa_rawmidi.cc
-                                        * which implements a complete parser per port without this limit.
-                                        */
-                                       continue;
-                               }
-                               first_time = false;
-
-                               if (midi_process_byte (data[mb])) {
-                                       midi_event_put ((void*)mbuf, time, _parser_buffer, _parser_bytes);
-                               }
-                       }
-#endif
-                       size = sizeof(data);
-               }
+                CoreMidiPort* port = dynamic_cast<CoreMidiPort*> (*it);
+                if (!port) {
+                        continue;
+                }
+                uint64_t time_ns;
+                uint8_t data[128]; // matches CoreMidi's MIDIPacket
+                size_t size = sizeof(data);
+                
+                while (_midiio->recv_event (i, nominal_time, time_ns, data, size)) {
+                        pframes_t time = floor((float) time_ns * _samplerate * 1e-9);
+                        assert (time < n_samples);
+                        port->parse_events (time, data, size);
+                        size = sizeof(data); /* prepare for next call to recv_event */
+                }
        }
 
        /* get audio */
@@ -2186,6 +2052,13 @@ CoreMidiPort::CoreMidiPort (CoreAudioBackend &b, const std::string& name, PortFl
        : CoreBackendPort (b, name, flags)
        , _n_periods (1)
        , _bufperiod (0)
+        , _event (0, 0)
+        , _first_time(true)
+        , _unbuffered_bytes(0)
+        , _total_bytes(0)
+        , _expected_bytes(0)
+        , _status_byte(0)
+
 {
        _buffer[0].clear ();
        _buffer[1].clear ();
@@ -2213,9 +2086,161 @@ void* CoreMidiPort::get_buffer (pframes_t /* nframes */)
                }
                std::sort ((_buffer[_bufperiod]).begin (), (_buffer[_bufperiod]).end (), MidiEventSorter());
        }
+        if (!_buffer[_bufperiod].empty()) {
+                fprintf (stderr, "COREMIDI: %s have data in buffer (%d events)\n", name().c_str(), _buffer[_bufperiod].size());
+        }
        return &(_buffer[_bufperiod]);
 }
 
+int
+CoreMidiPort::queue_event (
+        void* port_buffer,
+        pframes_t timestamp,
+        const uint8_t* buffer, size_t size)
+{
+       if (!buffer || !port_buffer) return -1;
+        _event._pending = false;
+       CoreMidiBuffer& dst = * static_cast<CoreMidiBuffer*>(port_buffer);
+       if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) {
+#ifndef NDEBUG
+               // nevermind, ::get_buffer() sorts events
+               fprintf (stderr, "CoreMidiBuffer: unordered event: %d > %d\n",
+                               (pframes_t)dst.back ()->timestamp (), timestamp);
+#endif
+       }
+        fprintf (stderr, "coremidi: queue event/buffer size %d @ %d\n", size, timestamp);
+       dst.push_back (boost::shared_ptr<CoreMidiEvent>(new CoreMidiEvent (timestamp, buffer, size)));
+       return 0;
+}
+
+void
+CoreMidiPort::parse_events (const uint64_t time, const uint8_t *data, const size_t size) 
+{
+        CoreMidiBuffer* mbuf = static_cast<CoreMidiBuffer*>(get_buffer(0));
+
+        mbuf->clear();
+        
+       if (_event._pending) {
+               if (queue_event (mbuf, _event._time, _parser_buffer, _event._size)) {
+                       return;
+               }
+       }
+
+       for (size_t i = 0; i < size; ++i) {
+               if (_first_time && !(data[i] & 0x80)) {
+                       continue;
+               }
+
+               _first_time = false; 
+                
+               if (process_byte(time, data[i])) {
+                       if (queue_event (mbuf, _event._time, _parser_buffer, _event._size)) {
+                               return;
+                       }
+               }
+       }
+}
+
+// based on JackMidiRawInputWriteQueue by Devin Anderson //
+bool
+CoreMidiPort::process_byte(const uint64_t time, const uint8_t byte)
+{
+       if (byte >= 0xf8) {
+               // Realtime
+               if (byte == 0xfd) {
+                       return false;
+               }
+               _parser_buffer[0] = byte;
+               prepare_byte_event(time, byte);
+               return true;
+       }
+       if (byte == 0xf7) {
+               // Sysex end
+               if (_status_byte == 0xf0) {
+                       record_byte(byte);
+                       return prepare_buffered_event(time);
+               }
+    _total_bytes = 0;
+    _unbuffered_bytes = 0;
+               _expected_bytes = 0;
+               _status_byte = 0;
+               return false;
+       }
+       if (byte >= 0x80) {
+               // Non-realtime status byte
+               if (_total_bytes) {
+                       printf ("CoreMidiPort: discarded bogus midi message\n");
+#if 0
+                       for (size_t i=0; i < _total_bytes; ++i) {
+                               printf("%02x ", _parser_buffer[i]);
+                       }
+                       printf("\n");
+#endif
+                       _total_bytes = 0;
+                       _unbuffered_bytes = 0;
+               }
+               _status_byte = byte;
+               switch (byte & 0xf0) {
+                       case 0x80:
+                       case 0x90:
+                       case 0xa0:
+                       case 0xb0:
+                       case 0xe0:
+                               // Note On, Note Off, Aftertouch, Control Change, Pitch Wheel
+                               _expected_bytes = 3;
+                               break;
+                       case 0xc0:
+                       case 0xd0:
+                               // Program Change, Channel Pressure
+                               _expected_bytes = 2;
+                               break;
+                       case 0xf0:
+                               switch (byte) {
+                                       case 0xf0:
+                                               // Sysex
+                                               _expected_bytes = 0;
+                                               break;
+                                       case 0xf1:
+                                       case 0xf3:
+                                               // MTC Quarter Frame, Song Select
+                                               _expected_bytes = 2;
+                                               break;
+                                       case 0xf2:
+                                               // Song Position
+                                               _expected_bytes = 3;
+                                               break;
+                                       case 0xf4:
+                                       case 0xf5:
+                                               // Undefined
+                                               _expected_bytes = 0;
+                                               _status_byte = 0;
+                                               return false;
+                                       case 0xf6:
+                                               // Tune Request
+                                               prepare_byte_event(time, byte);
+                                               _expected_bytes = 0;
+                                               _status_byte = 0;
+                                               return true;
+                               }
+               }
+               record_byte(byte);
+               return false;
+       }
+       // Data byte
+       if (! _status_byte) {
+               // Data bytes without a status will be discarded.
+               _total_bytes++;
+               _unbuffered_bytes++;
+               return false;
+       }
+       if (! _total_bytes) {
+               record_byte(_status_byte);
+       }
+       record_byte(byte);
+       return (_total_bytes == _expected_bytes) ? prepare_buffered_event(time) : false;
+}
+
+
 CoreMidiEvent::CoreMidiEvent (const pframes_t timestamp, const uint8_t* data, size_t size)
        : _size (size)
        , _timestamp (timestamp)
index c4cbed91d588bbdf1784aefcca75775d5363f9d5..5682cc696608c74ba9b5258e05f6e19682446d88 100644 (file)
@@ -149,10 +149,67 @@ class CoreMidiPort : public CoreBackendPort {
                void next_period() { if (_n_periods > 1) { get_buffer(0); _bufperiod = (_bufperiod + 1) % _n_periods; } }
                void set_n_periods(int n) { if (n > 0 && n < 3) { _n_periods = n; } }
 
+        void parse_events (const uint64_t time, const uint8_t *data, const size_t size);
+
        private:
                CoreMidiBuffer _buffer[2];
                int _n_periods;
                int _bufperiod;
+
+        int queue_event (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size);
+       bool process_byte (const uint64_t, const uint8_t);
+
+       void record_byte(uint8_t byte) {
+               if (_total_bytes < sizeof(_parser_buffer)) {
+                       _parser_buffer[_total_bytes] = byte;
+               } else {
+                       ++_unbuffered_bytes;
+               }
+               ++_total_bytes;
+       }
+
+       void prepare_byte_event(const uint64_t time, const uint8_t byte) {
+               _parser_buffer[0] = byte;
+               _event.prepare(time, 1);
+       }
+
+       bool prepare_buffered_event(const uint64_t time) {
+               const bool result = _unbuffered_bytes == 0;
+               if (result) {
+                       _event.prepare(time, _total_bytes);
+               }
+               _total_bytes = 0;
+               _unbuffered_bytes = 0;
+               if (_status_byte >= 0xf0) {
+                       _expected_bytes = 0;
+                       _status_byte = 0;
+               }
+               return result;
+       }
+
+       struct ParserEvent {
+               uint64_t _time;
+               size_t _size;
+               bool _pending;
+               ParserEvent (const uint64_t time, const size_t size)
+                       : _time(time)
+                       , _size(size)
+                       , _pending(false) {}
+
+               void prepare(const uint64_t time, const size_t size) {
+                       _time = time;
+                       _size = size;
+                       _pending = true;
+               }
+       } _event;
+
+       bool    _first_time;
+       size_t  _unbuffered_bytes;
+       size_t  _total_bytes;
+       size_t  _expected_bytes;
+       uint8_t _status_byte;
+       uint8_t _parser_buffer[1024];
+
 }; // class CoreMidiPort
 
 class CoreAudioBackend : public AudioBackend {
@@ -293,6 +350,7 @@ class CoreAudioBackend : public AudioBackend {
                /* MIDI */
                int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index);
                int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size);
+
                uint32_t get_midi_event_count (void* port_buffer);
                void     midi_clear (void* port_buffer);
 
@@ -449,46 +507,6 @@ class CoreAudioBackend : public AudioBackend {
                        return NULL;
                }
 
-#ifdef USE_MIDI_PARSER
-
-               bool midi_process_byte (const uint8_t);
-
-               void midi_record_byte (uint8_t byte) {
-                       if (_total_bytes < sizeof (_parser_buffer)) {
-                               _parser_buffer[_total_bytes] = byte;
-                       } else {
-                               ++_unbuffered_bytes;
-                       }
-                       ++_total_bytes;
-               }
-
-               void midi_prepare_byte_event (const uint8_t byte) {
-                       _parser_buffer[0] = byte;
-                       _parser_bytes = 1;
-               }
-
-               bool midi_prepare_buffered_event () {
-                       const bool result = _unbuffered_bytes == 0;
-                       if (result) {
-                               _parser_bytes = _total_bytes;
-                       }
-                       _total_bytes = 0;
-                       _unbuffered_bytes = 0;
-                       if (_status_byte >= 0xf0) {
-                               _expected_bytes = 0;
-                               _status_byte = 0;
-                       }
-                       return result;
-               }
-
-               size_t  _unbuffered_bytes;
-               size_t  _total_bytes;
-               size_t  _expected_bytes;
-               uint8_t _status_byte;
-               uint8_t _parser_buffer[128];
-               uint8_t _parser_bytes;
-#endif
-
 }; // class CoreAudioBackend
 
 } // namespace