LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces - BluetoothMIDI_Interface.cpp (source / functions) Hit Total Coverage
Test: ffed98f648fe78e7aa7bdd228474317d40dadbec Lines: 159 174 91.4 %
Date: 2022-05-28 15:22:59 Functions: 25 28 89.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #if !defined(ARDUINO) || defined(ESP32) || defined(DOXYGEN)
       2             : 
       3             : #include "BluetoothMIDI_Interface.hpp"
       4             : #include "BLEMIDI/ESP32/midi.h"
       5             : 
       6             : BEGIN_CS_NAMESPACE
       7             : 
       8             : // -------------------------------------------------------------------------- //
       9             : 
      10             : // The following section defines functions that send the MIDI BLE packets in
      11             : // the background.
      12             : 
      13          27 : void BluetoothMIDI_Interface::startSendingThread() {
      14             :     // As long as you didn't get the stop signal, wait for data to send
      15          78 :     auto send_loop = [this] {
      16          51 :         while (handleSendEvents())
      17             :             ; // loop
      18          54 :     };
      19             :     // Need larger stack than default, pin to non-Arduino core
      20          27 :     ScopedThreadConfig(4096, 3, true, "SendBLEMIDI", 0);
      21             :     // Launch the thread
      22          27 :     send_thread = std::thread(send_loop);
      23          27 : }
      24             : 
      25          51 : bool BluetoothMIDI_Interface::handleSendEvents() {
      26          51 :     lock_t lock(mtx);
      27             : 
      28             :     // Wait for a packet to be started (or for a stop signal)
      29         152 :     cv.wait(lock, [this] { return !packetbuilder.empty() || stop_sending; });
      30          51 :     bool keep_going = !stop_sending;
      31             :     // Wait for flush signal or timeout
      32         105 :     bool flushing = cv.wait_for(lock, timeout, [this] { return flushnow; });
      33             : 
      34             :     // Send the packet over BLE, empty the buffer, and update the buffer size
      35             :     // based on the MTU of the connected clients.
      36          51 :     if (!packetbuilder.empty())
      37          25 :         notifyMIDIBLE(packetbuilder.getPacket());
      38          51 :     packetbuilder.reset();
      39          51 :     packetbuilder.setCapacity(min_mtu - 3);
      40             : 
      41             :     // Notify the main thread that the flush was done
      42          51 :     if (flushing) {
      43          50 :         flushnow = false;
      44          50 :         lock.unlock();
      45          50 :         cv.notify_one();
      46             :     }
      47         102 :     return keep_going;
      48             : }
      49             : 
      50          23 : void BluetoothMIDI_Interface::flushImpl(lock_t &lock) {
      51          23 :     assert(lock.owns_lock());
      52             :     // No need to send empty packets
      53          23 :     if (packetbuilder.empty())
      54           0 :         return;
      55             : 
      56             :     // Tell the background sender thread to send the packet now
      57          23 :     flushnow = true;
      58          23 :     lock.unlock();
      59          23 :     cv.notify_one();
      60             : 
      61             :     // Wait for flush to complete (when the sender clears the flushnow flag)
      62          23 :     lock.lock();
      63          68 :     cv.wait(lock, [this] { return !flushnow; });
      64          23 :     assert(lock.owns_lock());
      65             : }
      66             : 
      67          29 : void BluetoothMIDI_Interface::stopSendingThread() {
      68             :     // Tell the sender that this is the last packet
      69          29 :     stop_sending = true;
      70             : 
      71             :     // Tell it to send the packet right now (flush)
      72          58 :     lock_t lock(mtx);
      73          29 :     flushnow = true;
      74          29 :     lock.unlock();
      75          29 :     cv.notify_one();
      76             : 
      77             :     // Wait for it to be sent, and join the thread when done
      78          29 :     if (send_thread.joinable())
      79          27 :         send_thread.join();
      80          29 : }
      81             : 
      82             : // -------------------------------------------------------------------------- //
      83             : 
      84             : #ifdef ARDUINO
      85             : void BluetoothMIDI_Interface::notifyMIDIBLE(
      86             :     const std::vector<uint8_t> &packet) {
      87             :     midi_notify(packet.data(), packet.size());
      88             : }
      89             : #endif
      90             : 
      91             : // -------------------------------------------------------------------------- //
      92             : 
      93             : // The following section implements the MIDI sending functions.
      94             : 
      95          11 : void BluetoothMIDI_Interface::sendChannelMessageImpl(ChannelMessage msg) {
      96          11 :     msg.hasTwoDataBytes() ? sendChannelMessageImpl3Bytes(msg)
      97           3 :                           : sendChannelMessageImpl2Bytes(msg);
      98          11 : }
      99             : 
     100           8 : void BluetoothMIDI_Interface::sendChannelMessageImpl3Bytes(ChannelMessage msg) {
     101             :     // BLE packets are sent asynchronously, so we need a lock to access the
     102             :     // packet buffer
     103          16 :     lock_t lock(mtx);
     104           8 :     uint16_t timestamp = millis();
     105             :     // Try adding the message to the current packet
     106           8 :     if (!packetbuilder.add3B(msg.header, msg.data1, msg.data2, timestamp)) {
     107             :         // If that doesn't work, flush the packet (send it now and wait until
     108             :         // it is sent)
     109           1 :         flushImpl(lock);
     110             :         // And then add it to the (now emtpy) buffer
     111           1 :         packetbuilder.add3B(msg.header, msg.data1, msg.data2, timestamp);
     112             :     }
     113             :     // Notify the packet sender that data has been added to the buffer
     114           8 :     lock.unlock();
     115           8 :     cv.notify_one();
     116           8 : }
     117             : 
     118           3 : void BluetoothMIDI_Interface::sendChannelMessageImpl2Bytes(ChannelMessage msg) {
     119             :     // For comments, see
     120             :     //   sendChannelMessageImpl3Bytes() above
     121           6 :     lock_t lock(mtx);
     122           3 :     uint16_t timestamp = millis();
     123           3 :     if (!packetbuilder.add2B(msg.header, msg.data1, timestamp)) {
     124           1 :         flushImpl(lock);
     125           1 :         packetbuilder.add2B(msg.header, msg.data1, timestamp);
     126             :     }
     127           3 :     lock.unlock();
     128           3 :     cv.notify_one();
     129           3 : }
     130             : 
     131           4 : void BluetoothMIDI_Interface::sendRealTimeImpl(RealTimeMessage msg) {
     132             :     // For comments, see
     133             :     //   sendChannelMessageImpl3Bytes() above
     134           8 :     lock_t lock(mtx);
     135           4 :     uint16_t timestamp = millis();
     136           4 :     if (!packetbuilder.addRealTime(msg.message, timestamp)) {
     137           1 :         flushImpl(lock);
     138           1 :         packetbuilder.addRealTime(msg.message, timestamp);
     139             :     }
     140           4 :     lock.unlock();
     141           4 :     cv.notify_one();
     142           4 : }
     143             : 
     144           3 : void BluetoothMIDI_Interface::sendSysCommonImpl(SysCommonMessage msg) {
     145             :     // For comments, see
     146             :     //   sendChannelMessageImpl3Bytes() above
     147           6 :     lock_t lock(mtx);
     148           3 :     uint16_t timestamp = millis();
     149           3 :     uint8_t num_data = msg.getNumberOfDataBytes();
     150           3 :     if (!packetbuilder.addSysCommon(num_data, msg.header, msg.data1, msg.data2,
     151             :                                     timestamp)) {
     152           1 :         flushImpl(lock);
     153           1 :         packetbuilder.addSysCommon(num_data, msg.header, msg.data1, msg.data2,
     154             :                                    timestamp);
     155             :     }
     156           3 :     lock.unlock();
     157           3 :     cv.notify_one();
     158           3 : }
     159             : 
     160           4 : void BluetoothMIDI_Interface::sendSysExImpl(SysExMessage msg) {
     161           8 :     lock_t lock(mtx);
     162             : 
     163           4 :     size_t length = msg.length;
     164           4 :     const uint8_t *data = msg.data;
     165           4 :     uint16_t timestamp = millis(); // BLE MIDI timestamp
     166             :     // TODO: I have no idea why, but the last byte gets cut off when the LSB
     167             :     // of the timestamp is 0x77 ... (Problem is probably in the BlueZ parser)
     168           4 :     if ((timestamp & 0x77) == 0x77)
     169           0 :         timestamp &= 0xFFFE;
     170             : 
     171             :     // Try adding at least the SysExStart header to the current packet
     172           4 :     if (!packetbuilder.addSysEx(data, length, timestamp)) {
     173             :         // If that didn't fit, flush the packet
     174           1 :         flushImpl(lock);
     175             :         // Add the first part of the SysEx message to this packet
     176           1 :         packetbuilder.addSysEx(data, length, timestamp);
     177             :     }
     178             :     // As long as there's data to be sent in the next packet
     179          10 :     while (data) {
     180             :         // Send the previous (full) packet
     181           6 :         flushImpl(lock);
     182             :         // And add the next part of the SysEx message to a continuation packet
     183           6 :         packetbuilder.continueSysEx(data, length, timestamp);
     184             :     }
     185             :     // Notify the packet sender that data has been added to the buffer
     186           4 :     lock.unlock();
     187           4 :     cv.notify_one();
     188           4 : }
     189             : 
     190             : // -------------------------------------------------------------------------- //
     191             : 
     192          13 : void BluetoothMIDI_Interface::parse(const uint8_t *const data,
     193             :                                     const size_t len) {
     194          13 :     auto mididata = BLEMIDIParser(data, len);
     195          13 :     MIDIReadEvent event = parser.pull(mididata);
     196             :     // TODO: add a timeout instead of busy waiting?
     197          36 :     while (event != MIDIReadEvent::NO_MESSAGE) {
     198          23 :         switch (event) {
     199          16 :             case MIDIReadEvent::CHANNEL_MESSAGE:
     200          32 :                 while (!queue.push(parser.getChannelMessage(),
     201          16 :                                    mididata.getTimestamp()))
     202           0 :                     std::this_thread::yield();
     203          16 :                 break;
     204           4 :             case MIDIReadEvent::SYSEX_CHUNK: // fallthrough
     205             :             case MIDIReadEvent::SYSEX_MESSAGE:
     206           8 :                 while (!queue.push(parser.getSysExMessage(),
     207           4 :                                    mididata.getTimestamp()))
     208           0 :                     std::this_thread::yield();
     209           4 :                 break;
     210           2 :             case MIDIReadEvent::REALTIME_MESSAGE:
     211           4 :                 while (!queue.push(parser.getRealTimeMessage(),
     212           2 :                                    mididata.getTimestamp()))
     213           0 :                     std::this_thread::yield();
     214           2 :                 break;
     215           1 :             case MIDIReadEvent::SYSCOMMON_MESSAGE:
     216           2 :                 while (!queue.push(parser.getSysCommonMessage(),
     217           1 :                                    mididata.getTimestamp()))
     218           0 :                     std::this_thread::yield();
     219           1 :                 break;
     220             :             case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE
     221             :             default: break;                        // LCOV_EXCL_LINE
     222             :         }
     223          23 :         event = parser.pull(mididata);
     224             :     }
     225          13 :     parser.cancelRunningStatus();
     226          13 : }
     227             : 
     228          34 : MIDIReadEvent BluetoothMIDI_Interface::read() {
     229             :     // Pop a new message from the queue
     230          34 :     if (!queue.pop(incomingMessage))
     231          11 :         return MIDIReadEvent::NO_MESSAGE;
     232          23 :     return incomingMessage.eventType;
     233             : }
     234             : 
     235          16 : ChannelMessage BluetoothMIDI_Interface::getChannelMessage() const {
     236          16 :     return incomingMessage.eventType == MIDIReadEvent::CHANNEL_MESSAGE
     237          16 :                ? incomingMessage.message.channelmessage
     238          32 :                : ChannelMessage(0, 0, 0);
     239             : }
     240             : 
     241           1 : SysCommonMessage BluetoothMIDI_Interface::getSysCommonMessage() const {
     242           1 :     return incomingMessage.eventType == MIDIReadEvent::SYSCOMMON_MESSAGE
     243           1 :                ? incomingMessage.message.syscommonmessage
     244           2 :                : SysCommonMessage(0, 0, 0);
     245             : }
     246             : 
     247           2 : RealTimeMessage BluetoothMIDI_Interface::getRealTimeMessage() const {
     248           2 :     return incomingMessage.eventType == MIDIReadEvent::REALTIME_MESSAGE
     249           2 :                ? incomingMessage.message.realtimemessage
     250           4 :                : RealTimeMessage(0);
     251             : }
     252             : 
     253           4 : SysExMessage BluetoothMIDI_Interface::getSysExMessage() const {
     254           4 :     auto evt = incomingMessage.eventType;
     255           4 :     bool hasSysEx = evt == MIDIReadEvent::SYSEX_MESSAGE ||
     256             :                     evt == MIDIReadEvent::SYSEX_CHUNK;
     257           4 :     return hasSysEx ? incomingMessage.message.sysexmessage
     258           8 :                     : SysExMessage(nullptr, 0);
     259             : }
     260             : 
     261           4 : uint16_t BluetoothMIDI_Interface::getTimestamp() const {
     262           4 :     return incomingMessage.timestamp;
     263             : }
     264             : 
     265             : // -------------------------------------------------------------------------- //
     266             : 
     267           8 : void BluetoothMIDI_Interface::updateMTU(uint16_t mtu) {
     268           8 :     uint16_t force_min_mtu_c = force_min_mtu;
     269           8 :     if (force_min_mtu_c == 0)
     270           0 :         min_mtu = mtu;
     271             :     else
     272           8 :         min_mtu = std::min(force_min_mtu_c, mtu);
     273             :     DEBUGFN(NAMEDVALUE(min_mtu));
     274          16 :     lock_t lock(mtx);
     275           8 :     if (packetbuilder.getSize() == 0)
     276           8 :         packetbuilder.setCapacity(min_mtu - 3);
     277           8 : }
     278             : 
     279           8 : void BluetoothMIDI_Interface::forceMinMTU(uint16_t mtu) {
     280           8 :     force_min_mtu = mtu;
     281           8 :     updateMTU(min_mtu);
     282           8 : }
     283             : 
     284             : // -------------------------------------------------------------------------- //
     285             : 
     286           0 : extern "C" void BluetoothMIDI_Interface_midi_mtu_callback(uint16_t mtu) {
     287           0 :     BluetoothMIDI_Interface::midi_mtu_callback(mtu);
     288           0 : }
     289             : 
     290           0 : extern "C" void BluetoothMIDI_Interface_midi_write_callback(const uint8_t *data,
     291             :                                                             size_t length) {
     292           0 :     BluetoothMIDI_Interface::midi_write_callback(data, length);
     293           0 : }
     294             : 
     295             : // -------------------------------------------------------------------------- //
     296             : 
     297           0 : void BluetoothMIDI_Interface::setName(const char *name) {
     298             : #ifdef ARDUINO
     299             :     set_midi_ble_name(name);
     300             : #else
     301             :     (void)name;
     302             : #endif
     303           0 : }
     304             : 
     305          27 : void BluetoothMIDI_Interface::begin() {
     306             : #ifdef ARDUINO
     307             :     midi_set_mtu_callback(BluetoothMIDI_Interface_midi_mtu_callback);
     308             :     midi_set_write_callback(BluetoothMIDI_Interface_midi_write_callback);
     309             :     DEBUGFN(F("Initializing BLE MIDI Interface"));
     310             :     if (!midi_init()) {
     311             :         ERROR(F("Error initializing BLE MIDI interface"), 0x2022);
     312             :         return;
     313             :     }
     314             : #endif
     315          27 :     startSendingThread();
     316          27 : }
     317             : 
     318          28 : void BluetoothMIDI_Interface::end() {
     319             : #ifdef ARDUINO
     320             :     DEBUGFN(F("Deinitializing BLE MIDI Interface"));
     321             :     if (!midi_deinit()) {
     322             :         ERROR(F("Error deinitializing BLE MIDI interface"), 0x2023);
     323             :         return;
     324             :     }
     325             : #endif
     326          28 : }
     327             : 
     328             : // -------------------------------------------------------------------------- //
     329             : 
     330             : BluetoothMIDI_Interface *BluetoothMIDI_Interface::instance = nullptr;
     331             : 
     332             : END_CS_NAMESPACE
     333             : 
     334             : #endif

Generated by: LCOV version 1.15