diff --git a/src/midi_UsbDefs.h b/src/midi_UsbDefs.h index b117bc4..0cc110e 100644 --- a/src/midi_UsbDefs.h +++ b/src/midi_UsbDefs.h @@ -80,8 +80,11 @@ struct CodeIndexNumbers case midi::SystemReset: return CodeIndexNumbers::singleByte; + // System Exclusive case midi::SystemExclusive: return CodeIndexNumbers::sysExStart; + case 0xf7: + return CodeIndexNumbers::sysExEnds1Byte; // System Common Messages case midi::TimeCodeQuarterFrame: @@ -119,7 +122,7 @@ struct CodeIndexNumbers case sysExEnds2Bytes: return 2; - case systemCommon1Byte: + case systemCommon1Byte: // also sysExEnds1Byte case singleByte: return 1; diff --git a/src/midi_UsbPacketInterface.hpp b/src/midi_UsbPacketInterface.hpp index 6d57518..609ba9f 100644 --- a/src/midi_UsbPacketInterface.hpp +++ b/src/midi_UsbPacketInterface.hpp @@ -40,6 +40,68 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) const byte cin = midi::CodeIndexNumbers::fromStatus(status); const byte messageLength = midi::CodeIndexNumbers::getSize(cin); + if (status == 0xf0) + { + // Start of SysEx, check if it can end in one go. + if (bufferLength == 2 && inBuffer.peek(1) == 0xf7) + { + outPacket.header = midi::CodeIndexNumbers::sysExEnds2Bytes; + outPacket.byte1 = status; + outPacket.byte2 = 0xf7; + outPacket.byte3 = 0x00; + inBuffer.pop(2); + return true; + } + if (bufferLength >= 3 && inBuffer.peek(2) == 0xf7) + { + outPacket.header = midi::CodeIndexNumbers::sysExEnds3Bytes; + outPacket.byte1 = status; + outPacket.byte2 = inBuffer.peek(1); + outPacket.byte3 = 0xf7; + inBuffer.pop(3); + return true; + } + } + + if ((status & 0x80) == 0x00) + { + // First byte is data, consider it's part of a running SysEx message. + // We look for the SysEx end byte in the next 2 bytes + // At this point, bufferLength should be 2 or more to continue. + if (bufferLength == 1) + { + return false; // Not enough data + } + if (bufferLength == 2) + { + const bool isSysExEnd = inBuffer.peek(1) == 0xf7; + if (!isSysExEnd) + { + return false; // Not enough data (eg: 0x12 0x42) + } + // eg: 0x42 0xf7 + outPacket.header = midi::CodeIndexNumbers::sysExEnds2Bytes; + outPacket.byte1 = status; + outPacket.byte2 = inBuffer.peek(1); + outPacket.byte3 = 0x00; + inBuffer.pop(2); + return true; + } + else + { + // bufferLength > 2 + const byte byte3 = inBuffer.peek(2); + outPacket.header = byte3 == 0xf7 + ? midi::CodeIndexNumbers::sysExEnds3Bytes + : midi::CodeIndexNumbers::sysExContinue; + outPacket.byte1 = status; + outPacket.byte2 = inBuffer.peek(1); + outPacket.byte3 = byte3; + inBuffer.pop(3); + return true; + } + } + if (bufferLength < messageLength) { return false; // Not enough data in the buffer to compose a full packet. } @@ -53,7 +115,6 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) return true; // todo: handle interleaved RealTime messages - // todo: handle SysEx } template diff --git a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp index 3ca614e..ee90d1e 100644 --- a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp +++ b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp @@ -304,6 +304,135 @@ TEST(MidiUsbPacketInterfaceTx, TimeCodeQuarterFrame) EXPECT_EQ(0, buffer.getLength()); } +// System Exclusive -- + +TEST(MidiUsbPacketInterfaceTx, SysExNotEnoughData) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0x12); + buffer.write(0x42); + EXPECT_FALSE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(2, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExSinglePacket) +{ + midiEventPacket_t packet; + Buffer buffer; + + // Two-byte SysEx (utterly useless) + buffer.write(0xf0); + buffer.write(0xf7); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds2Bytes, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0xf7, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); + + // Single-data byte SysEx (non-spec conformant ?) + buffer.write(0xf0); + buffer.write(0x12); + buffer.write(0xf7); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds3Bytes, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0xf7, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExTwoPackets) +{ + midiEventPacket_t packet; + Buffer buffer; + + const byte deviceIdentityRequest[6] = { + 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 + }; + + buffer.write(deviceIdentityRequest, 6); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x7e, packet.byte2); + EXPECT_EQ(0x7f, packet.byte3); + EXPECT_EQ(3, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds3Bytes, packet.header); + EXPECT_EQ(0x06, packet.byte1); + EXPECT_EQ(0x01, packet.byte2); + EXPECT_EQ(0xf7, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith1Byte) +{ + midiEventPacket_t packet; + Buffer buffer; + + const byte message[7] = { + 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7 + }; + + buffer.write(message, 7); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x01, packet.byte2); + EXPECT_EQ(0x02, packet.byte3); + EXPECT_EQ(4, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExContinue, packet.header); + EXPECT_EQ(0x03, packet.byte1); + EXPECT_EQ(0x04, packet.byte2); + EXPECT_EQ(0x05, packet.byte3); + EXPECT_EQ(1, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds1Byte, packet.header); + EXPECT_EQ(0xf7, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith2Bytes) +{ + midiEventPacket_t packet; + Buffer buffer; + + const byte message[8] = { + 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xf7 + }; + + buffer.write(message, 8); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x01, packet.byte2); + EXPECT_EQ(0x02, packet.byte3); + EXPECT_EQ(5, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExContinue, packet.header); + EXPECT_EQ(0x03, packet.byte1); + EXPECT_EQ(0x04, packet.byte2); + EXPECT_EQ(0x05, packet.byte3); + EXPECT_EQ(2, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds2Bytes, packet.header); + EXPECT_EQ(0x06, packet.byte1); + EXPECT_EQ(0xf7, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + // ----------------------------------------------------------------------------- TEST(MidiUsbPacketInterfaceRx, PacketParsing)