feat: Handle Korg-style of flipping SysEx data header bits

Closes #92
This commit is contained in:
Francois Best 2018-03-10 16:58:17 +01:00
parent 9b9905fb2a
commit 1c01effbd9
4 changed files with 267 additions and 108 deletions

87
doc/sysex-codec.md Normal file
View File

@ -0,0 +1,87 @@
# SysEx Encoding & Decoding
There are various ways of encoding & decoding arbitrary 8-bit wide data into
SysEx, which is 7-bit wide.
The [official documentation](http://www.somascape.org/midi/tech/spec.html#nusx_fd)
for FileDump data exchanges states the following:
> The 8-bit file data needs to be converted to 7-bit form,
> with the result that every 7 bytes of file data translates
> to 8 bytes in the MIDI stream.
>
> For each group of 7 bytes (of file data) the top bit from each
> is used to construct an eigth byte, which is sent first.
> So:
> ```
> AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg
> ```
> becomes:
> ```
> 0ABCDEFG 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg
> ```
>
> The final group may have less than 7 bytes, and is coded as follows
> (e.g. with 3 bytes in the final group):
> ```
> 0ABC0000 0AAAaaaa 0BBBbbbb 0CCCcccc
> ```
## SysEx encoding / decoding functions
The MIDI library supplies two functions to do this, `encodeSysEx` and `decodeSysEx`.
Example usage:
```c++
#include <MIDI.h>
static const byte myData[12] = {
// Hex dump: CAFEBABE BAADF00D FACADE42
0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xf0, 0x0d,
0xfa, 0xca, 0xde, 0x42
};
byte encoded[16];
const unsigned encodedSize = midi::encodeSysEx(myData, encoded, 12);
// Encoded hex dump: 07 4a 7e 3a 3e 3a 2d 70 07 0d 7a 4a 5e 42
byte decoded[12];
const unsigned decoded = midi::decodeSysEx(encoded, decoded, encodedSize);
```
## Special case for Korg devices
Korg apparently uses another convention for their SysEx encoding / decoding,
where:
```
AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg
```
becomes:
```
0GFEDCBA 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg
```
The order of the bits in the "header" byte is reversed.
To follow this beheaviour, set the inFlipHeaderBits argument to true.
Example:
```c++
void handleSysEx(byte* inData, unsigned inSize)
{
// SysEx body data starts at 3rd byte: F0 42 aa bb cc dd F7
// 42 being the hex value of the Korg SysEx ID.
const unsigned dataStartOffset = 2;
const unsigned encodedDataLength = inSize - 3; // Remove F0 42 & F7
// Create a large enough buffer where to decode the message
byte decodedData[64];
const unsigned decodedSize = decodeSysEx(inData + dataStartOffset,
decodedData,
encodedDataLength,
true); // flip header bits
// Do stuff with your message
}
```
See original discussion in issue #92.

View File

@ -38,11 +38,15 @@ BEGIN_MIDI_NAMESPACE
\param inData The data to encode. \param inData The data to encode.
\param outSysEx The output buffer where to store the encoded message. \param outSysEx The output buffer where to store the encoded message.
\param inLength The lenght of the input buffer. \param inLength The lenght of the input buffer.
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
\return The lenght of the encoded output buffer. \return The lenght of the encoded output buffer.
@see decodeSysEx @see decodeSysEx
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
*/ */
unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength) unsigned encodeSysEx(const byte* inData,
byte* outSysEx,
unsigned inLength,
bool inFlipHeaderBits)
{ {
unsigned outLength = 0; // Num bytes in output array. unsigned outLength = 0; // Num bytes in output array.
byte count = 0; // Num 7bytes in a block. byte count = 0; // Num 7bytes in a block.
@ -54,7 +58,7 @@ unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength)
const byte msb = data >> 7; const byte msb = data >> 7;
const byte body = data & 0x7f; const byte body = data & 0x7f;
outSysEx[0] |= (msb << (6 - count)); outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count)));
outSysEx[1 + count] = body; outSysEx[1 + count] = body;
if (count++ == 6) if (count++ == 6)
@ -75,11 +79,15 @@ unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength)
\param inSysEx The SysEx data received from MIDI in. \param inSysEx The SysEx data received from MIDI in.
\param outData The output buffer where to store the decrypted message. \param outData The output buffer where to store the decrypted message.
\param inLength The lenght of the input buffer. \param inLength The lenght of the input buffer.
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
\return The lenght of the output buffer. \return The lenght of the output buffer.
@see encodeSysEx @see getSysExArrayLength @see encodeSysEx @see getSysExArrayLength
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
*/ */
unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength) unsigned decodeSysEx(const byte* inSysEx,
byte* outData,
unsigned inLength,
bool inFlipHeaderBits)
{ {
unsigned count = 0; unsigned count = 0;
byte msbStorage = 0; byte msbStorage = 0;
@ -95,7 +103,9 @@ unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength)
else else
{ {
const byte body = inSysEx[i]; const byte body = inSysEx[i];
const byte msb = ((msbStorage >> byteIndex--) & 1) << 7; const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex;
const byte msb = ((msbStorage >> shift) & 1) << 7;
byteIndex--;
outData[count++] = msb | body; outData[count++] = msb | body;
} }
} }

View File

@ -250,8 +250,14 @@ private:
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLenght); unsigned encodeSysEx(const byte* inData,
unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLenght); byte* outSysEx,
unsigned inLenght,
bool inFlipHeaderBits = false);
unsigned decodeSysEx(const byte* inSysEx,
byte* outData,
unsigned inLenght,
bool inFlipHeaderBits = false);
END_MIDI_NAMESPACE END_MIDI_NAMESPACE

View File

@ -11,9 +11,7 @@ BEGIN_UNNAMED_NAMESPACE
using namespace testing; using namespace testing;
TEST(SysExCodec, Encoder) TEST(SysExCodec, EncoderAscii)
{
// ASCII content
{ {
const byte input[] = "Hello, World!"; const byte input[] = "Hello, World!";
byte buffer[16]; byte buffer[16];
@ -27,7 +25,8 @@ TEST(SysExCodec, Encoder)
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected)); EXPECT_THAT(buffer, ContainerEq(expected));
} }
// Non-ASCII content
TEST(SysExCodec, EncoderNonAscii)
{ {
const byte input[] = { const byte input[] = {
182, 236, 167, 177, 61, 91, 120, // 01111000 -> 120 182, 236, 167, 177, 61, 91, 120, // 01111000 -> 120
@ -45,11 +44,29 @@ TEST(SysExCodec, Encoder)
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected)); EXPECT_THAT(buffer, ContainerEq(expected));
} }
TEST(SysExCodec, EncoderNonAsciiFlipHeader)
{
const byte input[] = {
182, 236, 167, 177, 61, 91, 120, // 00011111 -> 15
107, 94, 209, 87, 94 // 0xx00100 -> 4
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12, true);
EXPECT_EQ(encodedSize, unsigned(14));
const byte expected[16] = {
// MSB Data
15, 54, 108, 39, 49, 61, 91, 120,
4, 107, 94, 81, 87, 94, 0, 0,
};
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
} }
TEST(SysExCodec, Decoder) // -----------------------------------------------------------------------------
{
// ASCII content TEST(SysExCodec, DecoderAscii)
{ {
const byte input[] = { const byte input[] = {
0, 'H', 'e', 'l', 'l', 'o', ',', ' ', 0, 'H', 'e', 'l', 'l', 'o', ',', ' ',
@ -66,7 +83,9 @@ TEST(SysExCodec, Decoder)
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected)); EXPECT_THAT(buffer, ContainerEq(expected));
} }
// Non-ASCII content // Non-ASCII content
TEST(SysExCodec, DecoderNonAscii)
{ {
const byte input[] = { const byte input[] = {
// MSB Data // MSB Data
@ -85,11 +104,30 @@ TEST(SysExCodec, Decoder)
EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127 EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected)); EXPECT_THAT(buffer, ContainerEq(expected));
} }
TEST(SysExCodec, DecoderNonAsciiFlipHeader)
{
const byte input[] = {
// MSB Data
15, 54, 108, 39, 49, 61, 91, 120,
4, 107, 94, 81, 87, 94,
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14, true);
EXPECT_EQ(encodedSize, unsigned(12));
const byte expected[16] = {
182, 236, 167, 177, 61, 91, 120,
107, 94, 209, 87, 94, 0, 0,
0, 0,
};
EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
} }
TEST(SysExCodec, Codec) // -----------------------------------------------------------------------------
{
// ASCII content TEST(SysExCodec, CodecAscii)
{ {
const byte input[] = "Hello, World!"; const byte input[] = "Hello, World!";
byte buffer1[16]; byte buffer1[16];
@ -103,7 +141,8 @@ TEST(SysExCodec, Codec)
EXPECT_STREQ(reinterpret_cast<const char*>(buffer2), EXPECT_STREQ(reinterpret_cast<const char*>(buffer2),
reinterpret_cast<const char*>(input)); reinterpret_cast<const char*>(input));
} }
// Non-ASCII content
TEST(SysExCodec, CodecNonAscii)
{ {
const byte input[] = { const byte input[] = {
// MSB Data // MSB Data
@ -120,6 +159,23 @@ TEST(SysExCodec, Codec)
EXPECT_EQ(decodedSize, unsigned(12)); EXPECT_EQ(decodedSize, unsigned(12));
EXPECT_THAT(buffer2, ContainerEq(input)); EXPECT_THAT(buffer2, ContainerEq(input));
} }
TEST(SysExCodec, CodecNonAsciiFlipHeader)
{
const byte input[] = {
// MSB Data
182, 236, 167, 177, 61, 91, 120,
107, 94, 209, 87, 94
};
byte buffer1[14];
byte buffer2[12];
memset(buffer1, 0, 14 * sizeof(byte));
memset(buffer2, 0, 12 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12, true);
EXPECT_EQ(encodedSize, unsigned(14));
const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize, true);
EXPECT_EQ(decodedSize, unsigned(12));
EXPECT_THAT(buffer2, ContainerEq(input));
} }
END_UNNAMED_NAMESPACE END_UNNAMED_NAMESPACE