mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
UMPMidi1ToBytestreamTranslator: Refactor to separate responsibilities between translator and extractor
This commit is contained in:
parent
835216c581
commit
dd3d555bb9
28 changed files with 661 additions and 197 deletions
|
|
@ -120,6 +120,7 @@ add_library( ${BINARY_NAME}
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
@ -2784,6 +2785,7 @@ set_source_files_properties(
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
|
|||
|
|
@ -225,6 +225,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -910,6 +910,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -225,6 +225,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -910,6 +910,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ add_library( ${BINARY_NAME}
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
@ -2353,6 +2354,7 @@ set_source_files_properties(
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -691,6 +691,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ add_library( ${BINARY_NAME}
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
@ -2539,6 +2540,7 @@ set_source_files_properties(
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
|
|||
|
|
@ -191,6 +191,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -766,6 +766,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -191,6 +191,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -766,6 +766,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ add_library( ${BINARY_NAME}
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
@ -2437,6 +2438,7 @@ set_source_files_properties(
|
|||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiBuffer.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiDataConcatenator_test.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.cpp"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiFile.h"
|
||||
"../../../../../modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp"
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -721,6 +721,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -199,6 +199,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -814,6 +814,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -199,6 +199,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -814,6 +814,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -182,6 +182,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -718,6 +718,9 @@
|
|||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiBuffer.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiDataConcatenator_test.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\..\modules\juce_audio_basics\midi\juce_MidiFile.cpp">
|
||||
<Filter>JUCE Modules\juce_audio_basics\midi</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -112,5 +112,6 @@
|
|||
|
||||
#if JUCE_UNIT_TESTS
|
||||
#include "utilities/juce_ADSR_test.cpp"
|
||||
#include "midi/juce_MidiDataConcatenator_test.cpp"
|
||||
#include "midi/ump/juce_UMP_test.cpp"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -35,6 +35,163 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
enum class SysexExtractorCallbackKind
|
||||
{
|
||||
notSysex,
|
||||
ongoingSysex,
|
||||
lastSysex,
|
||||
};
|
||||
|
||||
class BytestreamSysexExtractor
|
||||
{
|
||||
public:
|
||||
void reset()
|
||||
{
|
||||
state = RunningStatus{};
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void push (Span<const std::byte> bytes, Callback&& callback)
|
||||
{
|
||||
for (const auto pair : enumerate (bytes))
|
||||
{
|
||||
const auto index = pair.index;
|
||||
const auto byte = pair.value;
|
||||
|
||||
state = std::invoke ([&]() -> State
|
||||
{
|
||||
if (auto* inSysex = std::get_if<InSysex> (&state))
|
||||
{
|
||||
if (byte == std::byte { 0xf0 })
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::lastSysex,
|
||||
Span { bytes.data() + index - inSysex->numBytes, inSysex->numBytes });
|
||||
return InSysex { 1 };
|
||||
}
|
||||
|
||||
if (byte == std::byte { 0xf7 })
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::lastSysex,
|
||||
Span { bytes.data() + index - inSysex->numBytes, inSysex->numBytes + 1 });
|
||||
return RunningStatus{};
|
||||
}
|
||||
|
||||
if (isRealtimeMessage (byte))
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::ongoingSysex,
|
||||
Span { bytes.data() + index - inSysex->numBytes, inSysex->numBytes });
|
||||
callback (SysexExtractorCallbackKind::notSysex,
|
||||
Span { bytes.data() + index, 1 });
|
||||
return InSysex{};
|
||||
}
|
||||
|
||||
if (isStatusByte (byte))
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::lastSysex,
|
||||
Span { bytes.data() + index - inSysex->numBytes, inSysex->numBytes });
|
||||
return RunningStatus { 1, { byte } };
|
||||
}
|
||||
|
||||
return InSysex { inSysex->numBytes + 1 };
|
||||
}
|
||||
|
||||
if (auto* runningStatus = std::get_if<RunningStatus> (&state))
|
||||
{
|
||||
if (byte == std::byte { 0xf0 })
|
||||
return InSysex { 1 };
|
||||
|
||||
const auto nextRunningStatus = std::invoke ([&]
|
||||
{
|
||||
if (isRealtimeMessage (byte))
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::notSysex,
|
||||
Span { bytes.data() + index, 1 });
|
||||
return *runningStatus;
|
||||
}
|
||||
|
||||
if (isInitialByte (byte))
|
||||
return RunningStatus{}.withAppendedByte (byte);
|
||||
|
||||
if (0 < runningStatus->size && runningStatus->size < runningStatus->data.size())
|
||||
return runningStatus->withAppendedByte (byte);
|
||||
|
||||
// If we get to this branch, we're trying to process a non-status byte
|
||||
// without having seen any previous status byte, so ignore the current byte
|
||||
return RunningStatus{};
|
||||
});
|
||||
|
||||
if (const auto completeMessage = nextRunningStatus.getCompleteMessage(); ! completeMessage.empty())
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::notSysex,
|
||||
completeMessage);
|
||||
return RunningStatus{}.withAppendedByte (nextRunningStatus.data[0]);
|
||||
}
|
||||
|
||||
return nextRunningStatus;
|
||||
}
|
||||
|
||||
// Can only happen if the variant is valueless by exception, which indicates a much
|
||||
// more severe problem!
|
||||
std::terminate();
|
||||
});
|
||||
}
|
||||
|
||||
if (auto* inSysex = std::get_if<InSysex> (&state))
|
||||
{
|
||||
callback (SysexExtractorCallbackKind::ongoingSysex,
|
||||
Span { bytes.data() + bytes.size() - inSysex->numBytes, inSysex->numBytes });
|
||||
state = InSysex{};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static bool isRealtimeMessage (std::byte byte) { return std::byte (0xf8) <= byte && byte <= std::byte (0xfe); }
|
||||
static bool isStatusByte (std::byte byte) { return std::byte (0x80) <= byte; }
|
||||
static bool isInitialByte (std::byte byte) { return isStatusByte (byte) && byte != std::byte (0xf7); }
|
||||
|
||||
struct InSysex
|
||||
{
|
||||
size_t numBytes{};
|
||||
};
|
||||
|
||||
struct RunningStatus
|
||||
{
|
||||
// These constructors are required to work around a bug in GCC 7
|
||||
RunningStatus() {}
|
||||
|
||||
RunningStatus (uint8_t sizeIn, std::array<std::byte, 3> dataIn)
|
||||
: size (sizeIn), data (dataIn) {}
|
||||
|
||||
uint8_t size{};
|
||||
std::array<std::byte, 3> data{};
|
||||
|
||||
Span<const std::byte> getCompleteMessage() const
|
||||
{
|
||||
if (size == 0)
|
||||
return {};
|
||||
|
||||
const auto expectedSize = MidiMessage::getMessageLengthFromFirstByte ((uint8_t) data[0]);
|
||||
return Span { data.data(), size == expectedSize ? size : (size_t) 0 };
|
||||
}
|
||||
|
||||
void appendByte (std::byte x)
|
||||
{
|
||||
jassert (size < data.size());
|
||||
data[size++] = x;
|
||||
}
|
||||
|
||||
RunningStatus withAppendedByte (std::byte x) const
|
||||
{
|
||||
auto result = *this;
|
||||
result.appendByte (x);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
using State = std::variant<RunningStatus, InSysex>;
|
||||
State state;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Helper class that takes chunks of incoming midi bytes, packages them into
|
||||
|
|
@ -46,153 +203,93 @@ class MidiDataConcatenator
|
|||
{
|
||||
public:
|
||||
MidiDataConcatenator (int initialBufferSize)
|
||||
: pendingSysexData ((size_t) initialBufferSize)
|
||||
{
|
||||
pendingSysexData.reserve ((size_t) initialBufferSize);
|
||||
}
|
||||
|
||||
MidiDataConcatenator (MidiDataConcatenator&&) noexcept = default;
|
||||
MidiDataConcatenator& operator= (MidiDataConcatenator&&) noexcept = default;
|
||||
|
||||
void reset()
|
||||
{
|
||||
currentMessageLen = 0;
|
||||
pendingSysexSize = 0;
|
||||
extractor.reset();
|
||||
pendingSysexData.clear();
|
||||
pendingSysexTime = 0;
|
||||
}
|
||||
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void pushMidiData (const void* inputData, int numBytes, double time,
|
||||
UserDataType* input, CallbackType& callback)
|
||||
void pushMidiData (Span<const std::byte> bytes,
|
||||
double time,
|
||||
UserDataType* input,
|
||||
CallbackType& callback)
|
||||
{
|
||||
auto d = static_cast<const uint8*> (inputData);
|
||||
extractor.push (bytes, [&] (SysexExtractorCallbackKind kind, Span<const std::byte> bytesThisTime)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case SysexExtractorCallbackKind::notSysex:
|
||||
callback.handleIncomingMidiMessage (input,
|
||||
MidiMessage (bytesThisTime.data(),
|
||||
(int) bytesThisTime.size(),
|
||||
time));
|
||||
return;
|
||||
|
||||
while (numBytes > 0)
|
||||
case SysexExtractorCallbackKind::ongoingSysex:
|
||||
{
|
||||
auto nextByte = *d;
|
||||
if (pendingSysexData.empty())
|
||||
pendingSysexTime = time;
|
||||
|
||||
if (pendingSysexSize != 0 || nextByte == 0xf0)
|
||||
{
|
||||
processSysex (d, numBytes, time, input, callback);
|
||||
currentMessageLen = 0;
|
||||
continue;
|
||||
pendingSysexData.insert (pendingSysexData.end(), bytesThisTime.begin(), bytesThisTime.end());
|
||||
return;
|
||||
}
|
||||
|
||||
++d;
|
||||
--numBytes;
|
||||
|
||||
if (isRealtimeMessage (nextByte))
|
||||
case SysexExtractorCallbackKind::lastSysex:
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (nextByte, time));
|
||||
// These can be embedded in the middle of a normal message, so we won't
|
||||
// reset the currentMessageLen here.
|
||||
continue;
|
||||
pendingSysexData.insert (pendingSysexData.end(), bytesThisTime.begin(), bytesThisTime.end());
|
||||
|
||||
if (pendingSysexData.empty())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInitialByte (nextByte))
|
||||
if (pendingSysexData.back() == std::byte { 0xf7 })
|
||||
{
|
||||
currentMessage[0] = nextByte;
|
||||
currentMessageLen = 1;
|
||||
}
|
||||
else if (currentMessageLen > 0 && currentMessageLen < 3)
|
||||
{
|
||||
currentMessage[currentMessageLen++] = nextByte;
|
||||
callback.handleIncomingMidiMessage (input,
|
||||
MidiMessage (pendingSysexData.data(),
|
||||
(int) pendingSysexData.size(),
|
||||
pendingSysexTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
// message is too long or invalid MIDI - abandon it and start again with the next byte
|
||||
currentMessageLen = 0;
|
||||
continue;
|
||||
callback.handlePartialSysexMessage (input,
|
||||
unalignedPointerCast<const uint8*> (pendingSysexData.data()),
|
||||
(int) pendingSysexData.size(),
|
||||
pendingSysexTime);
|
||||
}
|
||||
|
||||
auto expectedLength = MidiMessage::getMessageLengthFromFirstByte (currentMessage[0]);
|
||||
pendingSysexData.clear();
|
||||
|
||||
if (expectedLength == currentMessageLen)
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void pushMidiData (const void* inputData,
|
||||
int numBytes,
|
||||
double time,
|
||||
UserDataType* input,
|
||||
CallbackType& callback)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (currentMessage, expectedLength, time));
|
||||
currentMessageLen = 1; // reset, but leave the first byte to use as the running status byte
|
||||
}
|
||||
}
|
||||
pushMidiData ({ static_cast<const std::byte*> (inputData), (size_t) numBytes }, time, input, callback);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void processSysex (const uint8*& d, int& numBytes, double time,
|
||||
UserDataType* input, CallbackType& callback)
|
||||
{
|
||||
if (*d == 0xf0)
|
||||
{
|
||||
pendingSysexSize = 0;
|
||||
pendingSysexTime = time;
|
||||
}
|
||||
|
||||
pendingSysexData.ensureSize ((size_t) (pendingSysexSize + numBytes), false);
|
||||
auto totalMessage = static_cast<uint8*> (pendingSysexData.getData());
|
||||
auto dest = totalMessage + pendingSysexSize;
|
||||
|
||||
do
|
||||
{
|
||||
if (pendingSysexSize > 0 && isStatusByte (*d))
|
||||
{
|
||||
if (*d == 0xf7)
|
||||
{
|
||||
*dest++ = *d++;
|
||||
++pendingSysexSize;
|
||||
--numBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*d >= 0xfa || *d == 0xf8)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time));
|
||||
++d;
|
||||
--numBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingSysexSize = 0;
|
||||
int used = 0;
|
||||
const MidiMessage m (d, numBytes, used, 0, time);
|
||||
|
||||
if (used > 0)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, m);
|
||||
numBytes -= used;
|
||||
d += used;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*dest++ = *d++;
|
||||
++pendingSysexSize;
|
||||
--numBytes;
|
||||
}
|
||||
}
|
||||
while (numBytes > 0);
|
||||
|
||||
if (pendingSysexSize > 0)
|
||||
{
|
||||
if (totalMessage [pendingSysexSize - 1] == 0xf7)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingSysexSize, pendingSysexTime));
|
||||
pendingSysexSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.handlePartialSysexMessage (input, totalMessage, pendingSysexSize, pendingSysexTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool isRealtimeMessage (uint8 byte) { return byte >= 0xf8 && byte <= 0xfe; }
|
||||
static bool isStatusByte (uint8 byte) { return byte >= 0x80; }
|
||||
static bool isInitialByte (uint8 byte) { return isStatusByte (byte) && byte != 0xf7; }
|
||||
|
||||
uint8 currentMessage[3];
|
||||
int currentMessageLen = 0;
|
||||
|
||||
MemoryBlock pendingSysexData;
|
||||
BytestreamSysexExtractor extractor;
|
||||
std::vector<std::byte> pendingSysexData;
|
||||
double pendingSysexTime = 0;
|
||||
int pendingSysexSize = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class BytestreamSysexExtractorTest : public UnitTest
|
||||
{
|
||||
public:
|
||||
BytestreamSysexExtractorTest()
|
||||
: UnitTest ("BytestreamSysexExtractor", UnitTestCategories::midi)
|
||||
{
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Passing empty buffer while no message is in progress does nothing");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
bool called = false;
|
||||
extractor.push ({}, [&] (auto&&...) { called = true; });
|
||||
|
||||
expect (! called);
|
||||
}
|
||||
|
||||
beginTest ("Passing sysex with no payload reports an empty message");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
bool called = false;
|
||||
const std::byte message[] { std::byte (0xf0), std::byte (0xf7) };
|
||||
extractor.push (message, [&] (auto status, auto bytes)
|
||||
{
|
||||
called = true;
|
||||
expect (status == SysexExtractorCallbackKind::lastSysex);
|
||||
expect (bytes.size() == 2 && bytes[0] == std::byte (0xf0) && bytes[1] == std::byte (0xf7));
|
||||
});
|
||||
|
||||
expect (called);
|
||||
}
|
||||
|
||||
beginTest ("Sending only the sysex starting byte reports an ongoing message");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
int numCalls = 0;
|
||||
const std::byte message[] { std::byte (0xf0) };
|
||||
extractor.push (message, [&] (auto status, auto bytes)
|
||||
{
|
||||
++numCalls;
|
||||
expect (status == SysexExtractorCallbackKind::ongoingSysex);
|
||||
expect (bytes.size() == 1 && bytes[0] == std::byte (0xf0));
|
||||
});
|
||||
|
||||
expect (numCalls == 1);
|
||||
|
||||
// Sending a subsequent empty span should report an ongoing message
|
||||
extractor.push (Span<const std::byte>{}, [&] (auto status, auto bytes)
|
||||
{
|
||||
++numCalls;
|
||||
expect (status == SysexExtractorCallbackKind::ongoingSysex);
|
||||
expect (bytes.empty());
|
||||
});
|
||||
|
||||
expect (numCalls == 2);
|
||||
}
|
||||
|
||||
beginTest ("Sending sysex interspersed with realtime messages filters out the realtime messages");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
const std::byte message[] { std::byte (0xf0),
|
||||
std::byte (0x50), // first data byte
|
||||
std::byte (0xfe), // active sensing
|
||||
std::byte (0x60), // second data byte
|
||||
std::byte (0x70), // third data byte
|
||||
std::byte (0xf7) };
|
||||
std::vector<std::vector<std::byte>> vectors;
|
||||
extractor.push (message, [&] (auto, auto bytes) { vectors.emplace_back (bytes.begin(), bytes.end()); });
|
||||
|
||||
expect (vectors == std::vector { std::vector { std::byte (0xf0), std::byte (0x50) },
|
||||
std::vector { std::byte (0xfe) },
|
||||
std::vector { std::byte (0x60), std::byte (0x70), std::byte (0xf7) } });
|
||||
}
|
||||
|
||||
beginTest ("Sending a second f0 byte during an ongoing sysex terminates the previous sysex");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
const std::byte message[] { std::byte (0xf0), // start of first sysex
|
||||
std::byte (0x00),
|
||||
std::byte (0x01),
|
||||
std::byte (0xf0), // start of second sysex
|
||||
std::byte (0x02),
|
||||
std::byte (0x03) };
|
||||
|
||||
std::vector<std::vector<std::byte>> vectors;
|
||||
extractor.push (message, [&] (auto, auto bytes) { vectors.emplace_back (bytes.begin(), bytes.end()); });
|
||||
|
||||
expect (vectors == std::vector { std::vector { std::byte (0xf0), std::byte (0x00), std::byte (0x01) },
|
||||
std::vector { std::byte (0xf0), std::byte (0x02), std::byte (0x03) } });
|
||||
}
|
||||
|
||||
beginTest ("Status bytes truncate ongoing sysex");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
const std::byte message[] { std::byte (0xf0), // start of first sysex
|
||||
std::byte (0x10),
|
||||
std::byte (0x20),
|
||||
std::byte (0x30),
|
||||
std::byte (0x80), // status byte
|
||||
std::byte (0x00),
|
||||
std::byte (0x00) };
|
||||
|
||||
std::vector<std::vector<std::byte>> vectors;
|
||||
extractor.push (message, [&] (auto, auto bytes) { vectors.emplace_back (bytes.begin(), bytes.end()); });
|
||||
|
||||
expect (vectors == std::vector { std::vector { std::byte (0xf0), std::byte (0x10), std::byte (0x20), std::byte (0x30) },
|
||||
std::vector { std::byte (0x80), std::byte (0x00), std::byte (0x00) } });
|
||||
}
|
||||
|
||||
beginTest ("Running status is preserved between calls");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
const std::byte message[] { std::byte (0x90), // note on
|
||||
std::byte (0x10),
|
||||
std::byte (0x20),
|
||||
std::byte (0x30),
|
||||
std::byte (0x40),
|
||||
std::byte (0x50) };
|
||||
|
||||
std::vector<std::vector<std::byte>> vectors;
|
||||
const auto callback = [&] (auto status, auto bytes)
|
||||
{
|
||||
expect (status == SysexExtractorCallbackKind::notSysex);
|
||||
vectors.emplace_back (bytes.begin(), bytes.end());
|
||||
};
|
||||
extractor.push (message, callback);
|
||||
extractor.push (std::array { std::byte (0x60) }, callback);
|
||||
|
||||
expect (vectors == std::vector { std::vector { std::byte (0x90), std::byte (0x10), std::byte (0x20) },
|
||||
std::vector { std::byte (0x90), std::byte (0x30), std::byte (0x40) },
|
||||
std::vector { std::byte (0x90), std::byte (0x50), std::byte (0x60) } });
|
||||
}
|
||||
|
||||
beginTest ("Realtime messages can intersperse bytes of non-sysex messages");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
const std::byte message[] { std::byte (0xd0), // channel pressure
|
||||
std::byte (0xfe), // active sensing
|
||||
std::byte (0x70), // pressure cont.
|
||||
std::byte (0xfe), // active sensing
|
||||
std::byte (0x60), // second pressure message
|
||||
std::byte (0xfe), // active sensing
|
||||
std::byte (0x50) }; // third pressure message
|
||||
|
||||
std::vector<std::vector<std::byte>> vectors;
|
||||
extractor.push (message, [&] (auto, auto bytes)
|
||||
{
|
||||
vectors.emplace_back (bytes.begin(), bytes.end());
|
||||
});
|
||||
|
||||
expect (vectors == std::vector { std::vector { std::byte (0xfe) },
|
||||
std::vector { std::byte (0xd0), std::byte (0x70) },
|
||||
std::vector { std::byte (0xfe) },
|
||||
std::vector { std::byte (0xd0), std::byte (0x60) },
|
||||
std::vector { std::byte (0xfe) },
|
||||
std::vector { std::byte (0xd0), std::byte (0x50) } });
|
||||
}
|
||||
|
||||
beginTest ("Non-status bytes with no associated running status are ignored");
|
||||
{
|
||||
BytestreamSysexExtractor extractor;
|
||||
const std::byte message[] { std::byte (0x10),
|
||||
std::byte (0x2e),
|
||||
std::byte (0x30),
|
||||
std::byte (0x4e),
|
||||
std::byte (0x80), // note off
|
||||
std::byte (0x0e),
|
||||
std::byte (0x00),
|
||||
std::byte (0xf0), // sysex
|
||||
std::byte (0xf7), // end sysex
|
||||
std::byte (0x00), // sysex resets running status
|
||||
std::byte (0x10), };
|
||||
|
||||
std::vector<std::vector<std::byte>> vectors;
|
||||
extractor.push (message, [&] (auto, auto bytes)
|
||||
{
|
||||
vectors.emplace_back (bytes.begin(), bytes.end());
|
||||
});
|
||||
|
||||
expect (vectors == std::vector { std::vector { std::byte (0x80), std::byte (0x0e), std::byte (0x00) },
|
||||
std::vector { std::byte (0xf0), std::byte (0xf7) } });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static BytestreamSysexExtractorTest bytestreamSysexExtractorTest;
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -189,7 +189,7 @@ namespace juce::universal_midi_packets
|
|||
|
||||
void reset() { translator.reset(); }
|
||||
|
||||
Midi1ToBytestreamTranslator translator;
|
||||
SingleGroupMidi1ToBytestreamTranslator translator;
|
||||
};
|
||||
} // namespace juce::universal_midi_packets
|
||||
/** @endcond */
|
||||
|
|
|
|||
|
|
@ -37,44 +37,39 @@ namespace juce::universal_midi_packets
|
|||
{
|
||||
|
||||
/**
|
||||
Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using
|
||||
the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages.
|
||||
|
||||
@tags{Audio}
|
||||
Extracts from a series of Universal MIDI Packets the bytes that are also meaningful in the
|
||||
bytestream MIDI 1.0 format.
|
||||
*/
|
||||
class Midi1ToBytestreamTranslator
|
||||
class SingleGroupMidi1ToBytestreamExtractor
|
||||
{
|
||||
public:
|
||||
/** Ensures that there is room in the internal buffer for a sysex message of at least
|
||||
`initialBufferSize` bytes.
|
||||
*/
|
||||
explicit Midi1ToBytestreamTranslator (int initialBufferSize)
|
||||
{
|
||||
pendingSysExData.reserve (size_t (initialBufferSize));
|
||||
}
|
||||
|
||||
/** Clears the concatenator. */
|
||||
void reset()
|
||||
{
|
||||
pendingSysExData.clear();
|
||||
pendingSysExTime = 0.0;
|
||||
sysexInProgress = false;
|
||||
}
|
||||
|
||||
/** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to
|
||||
an equivalent MidiMessage. Accumulates SysEx packets into a single
|
||||
MidiMessage, as appropriate.
|
||||
an equivalent MidiMessage. If the packet doesn't convert to a single bytestream message
|
||||
(as may be the case for long sysex7 data), then the the callback will be passed just the
|
||||
sysex bytes in the current packet. To reconstruct the entire sysex message, the caller
|
||||
can bytes that are marked as ongoingSysex, and process the full message once the callback
|
||||
receives bytes that are marked as lastSysex.
|
||||
|
||||
@param packet a packet which is using the MIDI 1.0 Protocol.
|
||||
@param time the timestamp to be applied to these messages.
|
||||
@param callback a callback which will be called with each converted MidiMessage.
|
||||
@param callback a callback that will be called with each converted MidiMessage.
|
||||
*/
|
||||
template <typename MessageCallback>
|
||||
void dispatch (const View& packet, double time, MessageCallback&& callback)
|
||||
{
|
||||
const auto firstWord = *packet.data();
|
||||
|
||||
if (! pendingSysExData.empty() && shouldPacketTerminateSysExEarly (firstWord))
|
||||
pendingSysExData.clear();
|
||||
if (sysexInProgress && shouldPacketTerminateSysExEarly (firstWord))
|
||||
{
|
||||
// unexpected end of last sysex
|
||||
callback (SysexExtractorCallbackKind::lastSysex, Span<const std::byte>());
|
||||
sysexInProgress = false;
|
||||
}
|
||||
|
||||
switch (packet.size())
|
||||
{
|
||||
|
|
@ -83,8 +78,10 @@ public:
|
|||
// Utility messages don't translate to bytestream format
|
||||
if (Utils::getMessageType (firstWord) != Utils::MessageKind::utility)
|
||||
{
|
||||
const auto message = fromUmp (PacketX1 { firstWord }, time);
|
||||
callback (BytestreamMidiView (&message));
|
||||
const auto converted = fromUmp (PacketX1 { firstWord }, time);
|
||||
callback (SysexExtractorCallbackKind::notSysex,
|
||||
Span (unalignedPointerCast<const std::byte*> (converted.getRawData()),
|
||||
(size_t) converted.getRawDataSize()));
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -93,7 +90,7 @@ public:
|
|||
case 2:
|
||||
{
|
||||
if (Utils::getMessageType (firstWord) == Utils::MessageKind::sysex7)
|
||||
processSysEx (PacketX2 { packet[0], packet[1] }, time, callback);
|
||||
processSysEx (PacketX2 { packet[0], packet[1] }, callback);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -120,69 +117,72 @@ public:
|
|||
const auto word = m.front();
|
||||
jassert (Utils::getNumWordsForMessageType (word) == 1);
|
||||
|
||||
const std::array<uint8_t, 3> bytes { { uint8_t ((word >> 0x10) & 0xff),
|
||||
uint8_t ((word >> 0x08) & 0xff),
|
||||
uint8_t ((word >> 0x00) & 0xff) } };
|
||||
const auto numBytes = MidiMessage::getMessageLengthFromFirstByte (bytes.front());
|
||||
const std::array<std::byte, 3> bytes { { std::byte ((word >> 0x10) & 0xff),
|
||||
std::byte ((word >> 0x08) & 0xff),
|
||||
std::byte ((word >> 0x00) & 0xff) } };
|
||||
const auto numBytes = MidiMessage::getMessageLengthFromFirstByte ((uint8_t) bytes.front());
|
||||
return MidiMessage (bytes.data(), numBytes, time);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename MessageCallback>
|
||||
void processSysEx (const PacketX2& packet,
|
||||
double time,
|
||||
MessageCallback&& callback)
|
||||
void processSysEx (const PacketX2& packet, MessageCallback&& callback)
|
||||
{
|
||||
switch (getSysEx7Kind (packet[0]))
|
||||
const std::array<std::byte, 1> initial { std::byte { 0xf0 } }, final { std::byte { 0xf7 } };
|
||||
std::array<std::byte, 8> storage{};
|
||||
size_t validBytes = 0;
|
||||
|
||||
const auto pushBytes = [&] (const Span<const std::byte> b)
|
||||
{
|
||||
std::copy (b.begin(), b.end(), storage.data() + validBytes);
|
||||
validBytes += b.size();
|
||||
};
|
||||
|
||||
const auto pushPacket = [&] (const PacketX2& p)
|
||||
{
|
||||
const auto newBytes = SysEx7::getDataBytes (p);
|
||||
pushBytes (Span<const std::byte> (newBytes.data.data(), newBytes.size));
|
||||
};
|
||||
|
||||
const auto kind = getSysEx7Kind (packet[0]);
|
||||
|
||||
if ( ( sysexInProgress && (kind == SysEx7::Kind::begin || kind == SysEx7::Kind::complete))
|
||||
|| (! sysexInProgress && (kind == SysEx7::Kind::continuation || kind == SysEx7::Kind::end)))
|
||||
{
|
||||
// Malformed SysEx, drop progress and return
|
||||
callback (SysexExtractorCallbackKind::lastSysex, Span<const std::byte>());
|
||||
sysexInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case SysEx7::Kind::complete:
|
||||
startSysExMessage (time);
|
||||
pushBytes (packet);
|
||||
terminateSysExMessage (callback);
|
||||
pushBytes (Span (initial));
|
||||
pushPacket (packet);
|
||||
pushBytes (Span (final));
|
||||
break;
|
||||
|
||||
case SysEx7::Kind::begin:
|
||||
startSysExMessage (time);
|
||||
pushBytes (packet);
|
||||
pushBytes (Span (initial));
|
||||
pushPacket (packet);
|
||||
break;
|
||||
|
||||
case SysEx7::Kind::continuation:
|
||||
if (pendingSysExData.empty())
|
||||
break;
|
||||
|
||||
pushBytes (packet);
|
||||
pushPacket (packet);
|
||||
break;
|
||||
|
||||
case SysEx7::Kind::end:
|
||||
if (pendingSysExData.empty())
|
||||
break;
|
||||
|
||||
pushBytes (packet);
|
||||
terminateSysExMessage (callback);
|
||||
pushPacket (packet);
|
||||
pushBytes (Span (final));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pushBytes (const PacketX2& packet)
|
||||
{
|
||||
const auto bytes = SysEx7::getDataBytes (packet);
|
||||
pendingSysExData.insert (pendingSysExData.end(),
|
||||
bytes.data.begin(),
|
||||
bytes.data.begin() + bytes.size);
|
||||
}
|
||||
|
||||
void startSysExMessage (double time)
|
||||
{
|
||||
pendingSysExTime = time;
|
||||
pendingSysExData.push_back (std::byte { 0xf0 });
|
||||
}
|
||||
|
||||
template <typename MessageCallback>
|
||||
void terminateSysExMessage (MessageCallback&& callback)
|
||||
{
|
||||
pendingSysExData.push_back (std::byte { 0xf7 });
|
||||
callback (BytestreamMidiView (pendingSysExData, pendingSysExTime));
|
||||
pendingSysExData.clear();
|
||||
sysexInProgress = sysexInProgress ? (kind == SysEx7::Kind::continuation)
|
||||
: (kind == SysEx7::Kind::begin);
|
||||
const auto callbackKind = sysexInProgress ? SysexExtractorCallbackKind::ongoingSysex
|
||||
: SysexExtractorCallbackKind::lastSysex;
|
||||
callback (callbackKind, Span (storage.data(), validBytes));
|
||||
}
|
||||
|
||||
static bool shouldPacketTerminateSysExEarly (uint32_t firstWord)
|
||||
|
|
@ -216,8 +216,85 @@ private:
|
|||
return Utils::getMessageType (word) == Utils::MessageKind::commonRealtime && ((word >> 0x10) & 0xff) >= 0xf8;
|
||||
}
|
||||
|
||||
std::vector<std::byte> pendingSysExData;
|
||||
bool sysexInProgress = false;
|
||||
};
|
||||
|
||||
/**
|
||||
Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using
|
||||
the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class SingleGroupMidi1ToBytestreamTranslator
|
||||
{
|
||||
public:
|
||||
/** Ensures that there is room in the internal buffer for a sysex message of at least
|
||||
initialBufferSize bytes.
|
||||
*/
|
||||
explicit SingleGroupMidi1ToBytestreamTranslator (int initialBufferSize)
|
||||
{
|
||||
pendingSysExData.reserve (size_t (initialBufferSize));
|
||||
}
|
||||
|
||||
/** Clears the concatenator. */
|
||||
void reset()
|
||||
{
|
||||
extractor.reset();
|
||||
pendingSysExData.clear();
|
||||
pendingSysExTime = 0.0;
|
||||
}
|
||||
|
||||
/** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to
|
||||
an equivalent MidiMessage. Accumulates SysEx packets into a single
|
||||
MidiMessage, as appropriate.
|
||||
|
||||
@param packet a packet which is using the MIDI 1.0 Protocol.
|
||||
@param time the timestamp to be applied to these messages.
|
||||
@param callback a callback which will be called with each converted MidiMessage.
|
||||
*/
|
||||
template <typename MessageCallback>
|
||||
void dispatch (const View& packet, double time, MessageCallback&& callback)
|
||||
{
|
||||
extractor.dispatch (packet, time, [&] (SysexExtractorCallbackKind kind, Span<const std::byte> bytes)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case SysexExtractorCallbackKind::notSysex:
|
||||
callback (BytestreamMidiView (bytes, time));
|
||||
return;
|
||||
|
||||
case SysexExtractorCallbackKind::ongoingSysex:
|
||||
{
|
||||
if (pendingSysExData.empty())
|
||||
pendingSysExTime = time;
|
||||
|
||||
pendingSysExData.insert (pendingSysExData.end(), bytes.begin(), bytes.end());
|
||||
return;
|
||||
}
|
||||
|
||||
case SysexExtractorCallbackKind::lastSysex:
|
||||
{
|
||||
pendingSysExData.insert (pendingSysExData.end(), bytes.begin(), bytes.end());
|
||||
|
||||
if (pendingSysExData.empty())
|
||||
return;
|
||||
|
||||
// If this is not true, then the sysex message was truncated somehow and we
|
||||
// probably shouldn't allow it to propagate
|
||||
if (pendingSysExData.back() == std::byte { 0xf7 })
|
||||
callback (BytestreamMidiView (Span<const std::byte> (pendingSysExData), pendingSysExTime));
|
||||
|
||||
pendingSysExData.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
SingleGroupMidi1ToBytestreamExtractor extractor;
|
||||
std::vector<std::byte> pendingSysExData;
|
||||
double pendingSysExTime = 0.0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public:
|
|||
|
||||
beginTest ("Short bytestream midi messages can be round-tripped through the UMP converter");
|
||||
{
|
||||
Midi1ToBytestreamTranslator translator (0);
|
||||
SingleGroupMidi1ToBytestreamTranslator translator (0);
|
||||
|
||||
forEachNonSysExTestMessage (random, [&] (const MidiMessage& m)
|
||||
{
|
||||
|
|
@ -208,7 +208,7 @@ public:
|
|||
{
|
||||
const auto newPacket = createRandomRealtimeUMP (random);
|
||||
modifiedPackets.add (View (newPacket.data()));
|
||||
realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0);
|
||||
realtimeMessages.addEvent (SingleGroupMidi1ToBytestreamExtractor::fromUmp (newPacket), 0);
|
||||
};
|
||||
|
||||
for (const auto& packet : originalPackets)
|
||||
|
|
@ -264,7 +264,7 @@ public:
|
|||
{
|
||||
const auto newPacket = createRandomRealtimeUMP (random);
|
||||
modifiedPackets.add (View (newPacket.data()));
|
||||
realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0);
|
||||
realtimeMessages.addEvent (SingleGroupMidi1ToBytestreamExtractor::fromUmp (newPacket), 0);
|
||||
};
|
||||
|
||||
const auto addRandomUtilityUMP = [&]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue