feat: Add SysEx support for USB MIDI
This commit is contained in:
parent
5b96487362
commit
1ccd7c3ce8
|
|
@ -80,8 +80,11 @@ struct CodeIndexNumbers
|
||||||
case midi::SystemReset:
|
case midi::SystemReset:
|
||||||
return CodeIndexNumbers::singleByte;
|
return CodeIndexNumbers::singleByte;
|
||||||
|
|
||||||
|
// System Exclusive
|
||||||
case midi::SystemExclusive:
|
case midi::SystemExclusive:
|
||||||
return CodeIndexNumbers::sysExStart;
|
return CodeIndexNumbers::sysExStart;
|
||||||
|
case 0xf7:
|
||||||
|
return CodeIndexNumbers::sysExEnds1Byte;
|
||||||
|
|
||||||
// System Common Messages
|
// System Common Messages
|
||||||
case midi::TimeCodeQuarterFrame:
|
case midi::TimeCodeQuarterFrame:
|
||||||
|
|
@ -119,7 +122,7 @@ struct CodeIndexNumbers
|
||||||
case sysExEnds2Bytes:
|
case sysExEnds2Bytes:
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
case systemCommon1Byte:
|
case systemCommon1Byte: // also sysExEnds1Byte
|
||||||
case singleByte:
|
case singleByte:
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,68 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket)
|
||||||
const byte cin = midi::CodeIndexNumbers::fromStatus(status);
|
const byte cin = midi::CodeIndexNumbers::fromStatus(status);
|
||||||
const byte messageLength = midi::CodeIndexNumbers::getSize(cin);
|
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) {
|
if (bufferLength < messageLength) {
|
||||||
return false; // Not enough data in the buffer to compose a full packet.
|
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;
|
return true;
|
||||||
|
|
||||||
// todo: handle interleaved RealTime messages
|
// todo: handle interleaved RealTime messages
|
||||||
// todo: handle SysEx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Buffer>
|
template<typename Buffer>
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,135 @@ TEST(MidiUsbPacketInterfaceTx, TimeCodeQuarterFrame)
|
||||||
EXPECT_EQ(0, buffer.getLength());
|
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)
|
TEST(MidiUsbPacketInterfaceRx, PacketParsing)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue