290 lines
8.7 KiB
C++
290 lines
8.7 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
namespace BlocksProtocol
|
|
{
|
|
|
|
/**
|
|
All sysex messages to or from a BLOCKS device begin with these header bytes.
|
|
The next byte that follows indicates the device index within the topology, where
|
|
the 0x40 bit is set for device->host messages, and clear for host->device messages.
|
|
The lower 6 bits contain the topology index of the destination or source device.
|
|
*/
|
|
static const uint8 roliSysexHeader[] = { 0xf0, 0x00, 0x21, 0x10, 0x77 };
|
|
|
|
|
|
static uint8 calculatePacketChecksum (const uint8* data, uint32 size) noexcept
|
|
{
|
|
uint8 checksum = (uint8) size;
|
|
|
|
for (uint32 i = 0; i < size; ++i)
|
|
checksum += checksum * 2 + data[i];
|
|
|
|
return checksum & 0x7f;
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
/**
|
|
Helper class to define an integer with a specific bit size.
|
|
|
|
@tags{Blocks}
|
|
*/
|
|
template <int numBits>
|
|
struct IntegerWithBitSize
|
|
{
|
|
IntegerWithBitSize() noexcept = default;
|
|
IntegerWithBitSize (const IntegerWithBitSize&) noexcept = default;
|
|
IntegerWithBitSize& operator= (const IntegerWithBitSize&) noexcept = default;
|
|
|
|
IntegerWithBitSize (uint32 v) noexcept : value (v)
|
|
{
|
|
static_assert (numBits <= 32, "numBits must be <= 32");
|
|
jassert (v >= 0 && v <= maxValue);
|
|
}
|
|
|
|
enum
|
|
{
|
|
bits = numBits,
|
|
maxValue = static_cast<uint32> ((1ULL << numBits) - 1ULL)
|
|
};
|
|
|
|
operator uint32() const noexcept { return value; }
|
|
uint32 get() const noexcept { return value; }
|
|
|
|
uint8 getScaledToByte() const noexcept
|
|
{
|
|
return (uint8) (numBits < 8 ? (uint32) (value << (8 - numBits))
|
|
: (uint32) (value >> (numBits - 8)));
|
|
}
|
|
|
|
float toUnipolarFloat() const noexcept { return value / (float) maxValue; }
|
|
float toBipolarFloat() const noexcept { return static_cast<int32> (value << (32 - numBits)) / (float) 0x80000000u; }
|
|
|
|
static IntegerWithBitSize fromUnipolarFloat (float value) noexcept
|
|
{
|
|
static_assert (numBits <= 31, "numBits must be <= 31");
|
|
return IntegerWithBitSize ((uint32) jlimit (0, (int) maxValue, (int) (value * maxValue)));
|
|
}
|
|
|
|
static IntegerWithBitSize fromBipolarFloat (float value) noexcept
|
|
{
|
|
static_assert (numBits <= 31, "numBits must be <= 31");
|
|
return IntegerWithBitSize (maxValue & (uint32) jlimit ((int) -(maxValue / 2), (int) (maxValue / 2), (int) (value * (maxValue / 2))));
|
|
}
|
|
|
|
uint32 value = 0;
|
|
};
|
|
|
|
//==============================================================================
|
|
/**
|
|
This helper class allocates a block of 7-bit bytes and can push sequences of bits into it.
|
|
@see Packed7BitArrayReader
|
|
|
|
@tags{Blocks}
|
|
*/
|
|
template <int allocatedBytes>
|
|
struct Packed7BitArrayBuilder
|
|
{
|
|
const void* getData() const noexcept { return data; }
|
|
int size() const noexcept { return bytesWritten + (bitsInCurrentByte > 0 ? 1 : 0); }
|
|
|
|
bool hasCapacity (int bitsNeeded) const noexcept
|
|
{
|
|
return ((bytesWritten + 2) * 7 + bitsInCurrentByte + bitsNeeded) <= allocatedBytes * 7;
|
|
}
|
|
|
|
void writeHeaderSysexBytes (uint8 deviceIndex) noexcept
|
|
{
|
|
jassert (bytesWritten + bitsInCurrentByte == 0);
|
|
|
|
for (int i = 0; i < (int) sizeof (roliSysexHeader); ++i)
|
|
data[bytesWritten++] = roliSysexHeader[i];
|
|
|
|
jassert (deviceIndex < 128);
|
|
data[bytesWritten++] = deviceIndex & 0x7f;
|
|
}
|
|
|
|
void writePacketSysexFooter() noexcept
|
|
{
|
|
if (bitsInCurrentByte != 0)
|
|
{
|
|
bitsInCurrentByte = 0;
|
|
++bytesWritten;
|
|
}
|
|
|
|
jassert (hasCapacity (0));
|
|
|
|
uint32 headerBytes = (uint32) sizeof (roliSysexHeader) + 1;
|
|
data[bytesWritten] = calculatePacketChecksum (data + headerBytes, (uint32) bytesWritten - headerBytes);
|
|
++bytesWritten;
|
|
|
|
data[bytesWritten++] = 0xf7;
|
|
}
|
|
|
|
template <int numBits>
|
|
Packed7BitArrayBuilder& operator<< (IntegerWithBitSize<numBits> value) noexcept
|
|
{
|
|
writeBits (value.value, numBits);
|
|
return *this;
|
|
}
|
|
|
|
void writeBits (uint32 value, int numBits) noexcept
|
|
{
|
|
jassert (numBits <= 32);
|
|
jassert (hasCapacity (numBits));
|
|
jassert (numBits == 32 || (value >> numBits) == 0);
|
|
|
|
while (numBits > 0)
|
|
{
|
|
if (bitsInCurrentByte == 0)
|
|
{
|
|
if (numBits < 7)
|
|
{
|
|
data[bytesWritten] = (uint8) value;
|
|
bitsInCurrentByte = numBits;
|
|
return;
|
|
}
|
|
|
|
if (numBits == 7)
|
|
{
|
|
data[bytesWritten++] = (uint8) value;
|
|
return;
|
|
}
|
|
|
|
data[bytesWritten++] = (uint8) (value & 0x7f);
|
|
value >>= 7;
|
|
numBits -= 7;
|
|
}
|
|
else
|
|
{
|
|
const int bitsToDo = jmin (7 - bitsInCurrentByte, numBits);
|
|
|
|
data[bytesWritten] |= ((value & ((1 << bitsToDo) - 1)) << bitsInCurrentByte);
|
|
value >>= bitsToDo;
|
|
numBits -= bitsToDo;
|
|
bitsInCurrentByte += bitsToDo;
|
|
|
|
if (bitsInCurrentByte == 7)
|
|
{
|
|
bitsInCurrentByte = 0;
|
|
++bytesWritten;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Describes the current building state */
|
|
struct State
|
|
{
|
|
int bytesWritten, bitsInCurrentByte;
|
|
};
|
|
|
|
State getState() const noexcept
|
|
{
|
|
return { bytesWritten, bitsInCurrentByte };
|
|
}
|
|
|
|
void restore (State state) noexcept
|
|
{
|
|
bytesWritten = state.bytesWritten;
|
|
bitsInCurrentByte = state.bitsInCurrentByte;
|
|
}
|
|
|
|
private:
|
|
uint8 data[allocatedBytes];
|
|
int bytesWritten = 0, bitsInCurrentByte = 0;
|
|
};
|
|
|
|
|
|
//==============================================================================
|
|
/**
|
|
This helper class reads from a block of 7-bit bytes as sequences of bits.
|
|
@see Packed7BitArrayBuilder
|
|
|
|
@tags{Blocks}
|
|
*/
|
|
struct Packed7BitArrayReader
|
|
{
|
|
Packed7BitArrayReader (const void* sourceData, int numBytes) noexcept
|
|
: data (static_cast<const uint8*> (sourceData)), totalBits (numBytes * 7)
|
|
{
|
|
}
|
|
|
|
int getRemainingBits() const noexcept
|
|
{
|
|
return totalBits - bitsReadInCurrentByte;
|
|
}
|
|
|
|
template <typename Target>
|
|
Target read() noexcept
|
|
{
|
|
return Target (readBits (Target::bits));
|
|
}
|
|
|
|
uint32 readBits (int numBits) noexcept
|
|
{
|
|
jassert (numBits <= 32);
|
|
jassert (getRemainingBits() >= numBits);
|
|
|
|
uint32 value = 0;
|
|
int bitsSoFar = 0;
|
|
|
|
while (numBits > 0)
|
|
{
|
|
const uint32 valueInCurrentByte = (*data >> bitsReadInCurrentByte);
|
|
|
|
const int bitsAvailable = 7 - bitsReadInCurrentByte;
|
|
|
|
if (bitsAvailable > numBits)
|
|
{
|
|
value |= ((valueInCurrentByte & ((1 << numBits) - 1)) << bitsSoFar);
|
|
bitsReadInCurrentByte += numBits;
|
|
break;
|
|
}
|
|
|
|
value |= (valueInCurrentByte << bitsSoFar);
|
|
numBits -= bitsAvailable;
|
|
bitsSoFar += bitsAvailable;
|
|
bitsReadInCurrentByte = 0;
|
|
++data;
|
|
totalBits -= 7;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static bool checksumIsOK (const uint8* data, uint32 size) noexcept
|
|
{
|
|
return size > 1 && calculatePacketChecksum (data, size - 1) == data[size - 1];
|
|
}
|
|
|
|
private:
|
|
const uint8* data;
|
|
int totalBits, bitsReadInCurrentByte = 0;
|
|
};
|
|
|
|
} // namespace BlocksProtocol
|
|
} // namespace juce
|