1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

MIDI-CI: Add module

This commit is contained in:
reuk 2023-08-25 13:31:23 +01:00
parent 387ab88c13
commit 8ebbc20311
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
85 changed files with 11013 additions and 16 deletions

View file

@ -81,8 +81,10 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"
@ -2153,8 +2155,10 @@ set_source_files_properties(
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"

View file

@ -2810,8 +2810,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3624,12 +3624,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -2810,8 +2810,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3624,12 +3624,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -2810,8 +2810,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3624,12 +3624,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -33,7 +33,8 @@
juce_audio_formats, juce_audio_processors, juce_audio_utils,
juce_core, juce_cryptography, juce_data_structures, juce_dsp,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra,
juce_opengl, juce_osc, juce_product_unlocking, juce_video
juce_opengl, juce_osc, juce_product_unlocking, juce_video,
juce_midi_ci
exporters: xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1,JUCE_PLUGINHOST_VST3=1,JUCE_PLUGINHOST_LV2=1

View file

@ -53,8 +53,10 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"
@ -1825,8 +1827,10 @@ set_source_files_properties(
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"

View file

@ -2414,8 +2414,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3048,12 +3048,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -86,8 +86,10 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"
@ -2011,8 +2013,10 @@ set_source_files_properties(
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"

View file

@ -2594,8 +2594,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3324,12 +3324,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -2594,8 +2594,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3324,12 +3324,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -2594,8 +2594,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3324,12 +3324,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -57,8 +57,10 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"
@ -1909,8 +1911,10 @@ set_source_files_properties(
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPacket.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPackets.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPBytesOnGroup.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConversion.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPConverters.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDeviceInfo.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPFactory.h"
"../../../../../modules/juce_audio_basics/midi/ump/juce_UMPIterator.cpp"

View file

@ -2505,8 +2505,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3189,12 +3189,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -282,7 +282,8 @@ StringArray getJUCEModules() noexcept
"juce_opengl",
"juce_osc",
"juce_product_unlocking",
"juce_video"
"juce_video",
"juce_midi_ci"
};
return juceModuleIds;

View file

@ -39,7 +39,7 @@ ifeq ($(CONFIG),Debug)
TARGET_ARCH :=
endif
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=0" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70008" "-DJUCE_MODULE_AVAILABLE_juce_analytics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_cryptography=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_dsp=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_MODULE_AVAILABLE_juce_opengl=1" "-DJUCE_MODULE_AVAILABLE_juce_osc=1" "-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_PLUGINHOST_VST3=1" "-DJUCE_PLUGINHOST_LV2=1" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_STANDALONE_APPLICATION=1" "-DJUCE_UNIT_TESTS=1" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell $(PKG_CONFIG) --cflags alsa freetype2 gl libcurl webkit2gtk-4.0 gtk+-x11-3.0) -pthread -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../../../modules/juce_audio_processors/format_types/LV2_SDK -I../../../../modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../../../modules $(CPPFLAGS)
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=0" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70008" "-DJUCE_MODULE_AVAILABLE_juce_analytics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_cryptography=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_dsp=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_MODULE_AVAILABLE_juce_midi_ci=1" "-DJUCE_MODULE_AVAILABLE_juce_opengl=1" "-DJUCE_MODULE_AVAILABLE_juce_osc=1" "-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_PLUGINHOST_VST3=1" "-DJUCE_PLUGINHOST_LV2=1" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_STANDALONE_APPLICATION=1" "-DJUCE_UNIT_TESTS=1" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell $(PKG_CONFIG) --cflags alsa freetype2 gl libcurl webkit2gtk-4.0 gtk+-x11-3.0) -pthread -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../../../modules/juce_audio_processors/format_types/LV2_SDK -I../../../../modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../../../modules $(CPPFLAGS)
JUCE_CPPFLAGS_CONSOLEAPP := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=0"
JUCE_TARGET_CONSOLEAPP := UnitTestRunner
@ -60,7 +60,7 @@ ifeq ($(CONFIG),Release)
TARGET_ARCH :=
endif
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=0" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70008" "-DJUCE_MODULE_AVAILABLE_juce_analytics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_cryptography=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_dsp=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_MODULE_AVAILABLE_juce_opengl=1" "-DJUCE_MODULE_AVAILABLE_juce_osc=1" "-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_PLUGINHOST_VST3=1" "-DJUCE_PLUGINHOST_LV2=1" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_STANDALONE_APPLICATION=1" "-DJUCE_UNIT_TESTS=1" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell $(PKG_CONFIG) --cflags alsa freetype2 gl libcurl webkit2gtk-4.0 gtk+-x11-3.0) -pthread -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../../../modules/juce_audio_processors/format_types/LV2_SDK -I../../../../modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../../../modules $(CPPFLAGS)
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=0" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70008" "-DJUCE_MODULE_AVAILABLE_juce_analytics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_cryptography=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_dsp=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_MODULE_AVAILABLE_juce_midi_ci=1" "-DJUCE_MODULE_AVAILABLE_juce_opengl=1" "-DJUCE_MODULE_AVAILABLE_juce_osc=1" "-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_PLUGINHOST_VST3=1" "-DJUCE_PLUGINHOST_LV2=1" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_STANDALONE_APPLICATION=1" "-DJUCE_UNIT_TESTS=1" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell $(PKG_CONFIG) --cflags alsa freetype2 gl libcurl webkit2gtk-4.0 gtk+-x11-3.0) -pthread -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../../../modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../../../modules/juce_audio_processors/format_types/LV2_SDK -I../../../../modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../../../modules $(CPPFLAGS)
JUCE_CPPFLAGS_CONSOLEAPP := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=0"
JUCE_TARGET_CONSOLEAPP := UnitTestRunner
@ -89,6 +89,7 @@ OBJECTS_CONSOLEAPP := \
$(JUCE_OBJDIR)/include_juce_graphics_f817e147.o \
$(JUCE_OBJDIR)/include_juce_gui_basics_e3f79785.o \
$(JUCE_OBJDIR)/include_juce_gui_extra_6dee1c1a.o \
$(JUCE_OBJDIR)/include_juce_midi_ci_1fda4092.o \
$(JUCE_OBJDIR)/include_juce_opengl_a8a032b.o \
$(JUCE_OBJDIR)/include_juce_osc_f3df604d.o \
$(JUCE_OBJDIR)/include_juce_product_unlocking_8278fcdc.o \
@ -191,6 +192,11 @@ $(JUCE_OBJDIR)/include_juce_gui_extra_6dee1c1a.o: ../../JuceLibraryCode/include_
@echo "Compiling include_juce_gui_extra.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_CONSOLEAPP) $(JUCE_CFLAGS_CONSOLEAPP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_midi_ci_1fda4092.o: ../../JuceLibraryCode/include_juce_midi_ci.cpp
-$(V_AT)mkdir -p $(@D)
@echo "Compiling include_juce_midi_ci.cpp"
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_CONSOLEAPP) $(JUCE_CFLAGS_CONSOLEAPP) -o "$@" -c "$<"
$(JUCE_OBJDIR)/include_juce_opengl_a8a032b.o: ../../JuceLibraryCode/include_juce_opengl.cpp
-$(V_AT)mkdir -p $(@D)
@echo "Compiling include_juce_opengl.cpp"

View file

@ -37,6 +37,7 @@
A70F7F4891DB1CF67653BE74 /* Accelerate.framework */ = {isa = PBXBuildFile; fileRef = B38A1AC42B002115350C0268; };
AA207299991F85938465BF65 /* Cocoa.framework */ = {isa = PBXBuildFile; fileRef = 2030A589A9355FE6A0F72428; };
AF1FE82A4A20DCB8944B35C7 /* include_juce_gui_extra.mm */ = {isa = PBXBuildFile; fileRef = 4195CB317C364D778AE2ADB1; };
B407D123F08A9A8C12624ABA /* include_juce_midi_ci.cpp */ = {isa = PBXBuildFile; fileRef = 0EFA505235D959565503D537; };
BFED026CA071070CEB87CFB5 /* include_juce_audio_basics.mm */ = {isa = PBXBuildFile; fileRef = 4BD792956FE7C22CB8FB691D; };
D17BAE3D36BB94FC2C8E2438 /* Main.cpp */ = {isa = PBXBuildFile; fileRef = 88AA2B9840A6792BBAD559EE; };
D43289CF624A7B068237C192 /* include_juce_gui_basics.mm */ = {isa = PBXBuildFile; fileRef = 583EA0E5C4B75A629AEF1157; };
@ -52,6 +53,7 @@
05501801BF6C4A47598C59E2 /* juce_cryptography */ /* juce_cryptography */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_cryptography; path = ../../../../modules/juce_cryptography; sourceTree = SOURCE_ROOT; };
080EAB9CF5AB2BD6B2BBB173 /* ConsoleApp */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = UnitTestRunner; sourceTree = BUILT_PRODUCTS_DIR; };
08ED235CBE02E0FB4BE4653E /* include_juce_cryptography.mm */ /* include_juce_cryptography.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_cryptography.mm; path = ../../JuceLibraryCode/include_juce_cryptography.mm; sourceTree = SOURCE_ROOT; };
0EFA505235D959565503D537 /* include_juce_midi_ci.cpp */ /* include_juce_midi_ci.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_midi_ci.cpp; path = ../../JuceLibraryCode/include_juce_midi_ci.cpp; sourceTree = SOURCE_ROOT; };
1CA82C74AEC08421812BDCAC /* include_juce_opengl.mm */ /* include_juce_opengl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_opengl.mm; path = ../../JuceLibraryCode/include_juce_opengl.mm; sourceTree = SOURCE_ROOT; };
1DC921E6494548F5E73E1056 /* juce_graphics */ /* juce_graphics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_graphics; path = ../../../../modules/juce_graphics; sourceTree = SOURCE_ROOT; };
2030A589A9355FE6A0F72428 /* Cocoa.framework */ /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
@ -82,6 +84,7 @@
88AA2B9840A6792BBAD559EE /* Main.cpp */ /* Main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Main.cpp; path = ../../Source/Main.cpp; sourceTree = SOURCE_ROOT; };
8C449538B266A891147103D6 /* IOKit.framework */ /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
8EBA9CF0874619A8FA0B4E74 /* juce_osc */ /* juce_osc */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_osc; path = ../../../../modules/juce_osc; sourceTree = SOURCE_ROOT; };
8EC828FBFEC92A64A135467C /* juce_midi_ci */ /* juce_midi_ci */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_midi_ci; path = ../../../../modules/juce_midi_ci; sourceTree = SOURCE_ROOT; };
A40A2A0B2841A622C53047CD /* juce_audio_processors */ /* juce_audio_processors */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_processors; path = ../../../../modules/juce_audio_processors; sourceTree = SOURCE_ROOT; };
A59D9064C3A2D7EC3DC45420 /* include_juce_osc.cpp */ /* include_juce_osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_osc.cpp; path = ../../JuceLibraryCode/include_juce_osc.cpp; sourceTree = SOURCE_ROOT; };
A76DD7182C290A9020C96CA7 /* include_juce_audio_formats.mm */ /* include_juce_audio_formats.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_formats.mm; path = ../../JuceLibraryCode/include_juce_audio_formats.mm; sourceTree = SOURCE_ROOT; };
@ -171,6 +174,7 @@
EECBAA403D2D6AEEA8CB05EB,
583EA0E5C4B75A629AEF1157,
4195CB317C364D778AE2ADB1,
0EFA505235D959565503D537,
1CA82C74AEC08421812BDCAC,
A59D9064C3A2D7EC3DC45420,
B96EC82EC3D2813B50386198,
@ -241,6 +245,7 @@
1DC921E6494548F5E73E1056,
DD849A04E38279B842EDE213,
2A163F48282EEE95B8A8BA7A,
8EC828FBFEC92A64A135467C,
CC27F53A76BFB2675D2683A1,
8EBA9CF0874619A8FA0B4E74,
748F996DD2778AD1442AECA6,
@ -327,6 +332,7 @@
A1A39E64F9E03EFFA10B0A10,
D43289CF624A7B068237C192,
AF1FE82A4A20DCB8944B35C7,
B407D123F08A9A8C12624ABA,
1D06F1A254F84A7AE3E90DF2,
7164274FE42C7EC423455E05,
1B09834E81EAF5BCB87FAAF4,
@ -416,6 +422,7 @@
"JUCE_MODULE_AVAILABLE_juce_graphics=1",
"JUCE_MODULE_AVAILABLE_juce_gui_basics=1",
"JUCE_MODULE_AVAILABLE_juce_gui_extra=1",
"JUCE_MODULE_AVAILABLE_juce_midi_ci=1",
"JUCE_MODULE_AVAILABLE_juce_opengl=1",
"JUCE_MODULE_AVAILABLE_juce_osc=1",
"JUCE_MODULE_AVAILABLE_juce_product_unlocking=1",
@ -548,6 +555,7 @@
"JUCE_MODULE_AVAILABLE_juce_graphics=1",
"JUCE_MODULE_AVAILABLE_juce_gui_basics=1",
"JUCE_MODULE_AVAILABLE_juce_gui_extra=1",
"JUCE_MODULE_AVAILABLE_juce_midi_ci=1",
"JUCE_MODULE_AVAILABLE_juce_opengl=1",
"JUCE_MODULE_AVAILABLE_juce_osc=1",
"JUCE_MODULE_AVAILABLE_juce_product_unlocking=1",

View file

@ -64,7 +64,7 @@
<Optimization>Disabled</Optimization>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
@ -80,7 +80,7 @@
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<OutputFile>$(OutDir)\UnitTestRunner.exe</OutputFile>
@ -108,7 +108,7 @@
<ClCompile>
<Optimization>Full</Optimization>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
@ -124,7 +124,7 @@
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<OutputFile>$(OutDir)\UnitTestRunner.exe</OutputFile>
@ -2525,6 +2525,42 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_opengl\opengl\juce_gl.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
@ -2631,6 +2667,7 @@
<AdditionalOptions> /bigobj %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_midi_ci.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_opengl.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_osc.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_product_unlocking.cpp"/>
@ -2650,8 +2687,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>
@ -3634,6 +3673,37 @@
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\misc\juce_WebBrowserComponent.h"/>
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\native\juce_NSViewFrameWatcher_mac.h"/>
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIChannelAddress.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceFeatures.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceListener.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceMessageHandler.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceOptions.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncoding.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIFunctionBlock.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMessages.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMuid.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileAtAddress.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeResult.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISubscription.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISupportedAndActive.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMarshalling.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageMeta.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageTypeUtils.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyHostUtils.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Draggable3DOrientation.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Matrix3D.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Quaternion.h"/>

View file

@ -623,6 +623,15 @@
<Filter Include="JUCE Modules\juce_gui_extra">
<UniqueIdentifier>{A4D76113-9EDC-DA60-D89B-5BACF7F1C426}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci\ci">
<UniqueIdentifier>{EC0A49B5-F336-1F4D-6C32-40E19BE1426F}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci\detail">
<UniqueIdentifier>{39CDBE58-7B8F-B367-DAE9-BCA326A4C637}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci">
<UniqueIdentifier>{F64F0BED-92DA-A4AE-0A76-9AC4FC01C199}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_opengl\geometry">
<UniqueIdentifier>{1A9221A3-E993-70B2-6EA2-8E1DB5FF646A}</UniqueIdentifier>
</Filter>
@ -3196,6 +3205,42 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.mm">
<Filter>JUCE Modules\juce_gui_extra</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.cpp">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.cpp">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.cpp">
<Filter>JUCE Modules\juce_midi_ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_opengl\opengl\juce_gl.cpp">
<Filter>JUCE Modules\juce_opengl\opengl</Filter>
</ClCompile>
@ -3334,6 +3379,9 @@
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_midi_ci.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_opengl.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
@ -3387,12 +3435,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
@ -6339,6 +6393,99 @@
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.h">
<Filter>JUCE Modules\juce_gui_extra</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIChannelAddress.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceFeatures.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceListener.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceMessageHandler.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceOptions.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncoding.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIFunctionBlock.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMessages.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMuid.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileAtAddress.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeResult.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISubscription.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISupportedAndActive.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMarshalling.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageMeta.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageTypeUtils.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyHostUtils.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.h">
<Filter>JUCE Modules\juce_midi_ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Draggable3DOrientation.h">
<Filter>JUCE Modules\juce_opengl\geometry</Filter>
</ClInclude>

View file

@ -64,7 +64,7 @@
<Optimization>Disabled</Optimization>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
@ -80,7 +80,7 @@
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<OutputFile>$(OutDir)\UnitTestRunner.exe</OutputFile>
@ -108,7 +108,7 @@
<ClCompile>
<Optimization>Full</Optimization>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
@ -124,7 +124,7 @@
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<OutputFile>$(OutDir)\UnitTestRunner.exe</OutputFile>
@ -2525,6 +2525,42 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_opengl\opengl\juce_gl.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
@ -2631,6 +2667,7 @@
<AdditionalOptions> /bigobj %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_midi_ci.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_opengl.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_osc.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_product_unlocking.cpp"/>
@ -2650,8 +2687,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>
@ -3634,6 +3673,37 @@
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\misc\juce_WebBrowserComponent.h"/>
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\native\juce_NSViewFrameWatcher_mac.h"/>
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIChannelAddress.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceFeatures.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceListener.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceMessageHandler.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceOptions.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncoding.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIFunctionBlock.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMessages.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMuid.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileAtAddress.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeResult.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISubscription.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISupportedAndActive.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMarshalling.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageMeta.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageTypeUtils.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyHostUtils.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Draggable3DOrientation.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Matrix3D.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Quaternion.h"/>

View file

@ -623,6 +623,15 @@
<Filter Include="JUCE Modules\juce_gui_extra">
<UniqueIdentifier>{A4D76113-9EDC-DA60-D89B-5BACF7F1C426}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci\ci">
<UniqueIdentifier>{EC0A49B5-F336-1F4D-6C32-40E19BE1426F}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci\detail">
<UniqueIdentifier>{39CDBE58-7B8F-B367-DAE9-BCA326A4C637}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci">
<UniqueIdentifier>{F64F0BED-92DA-A4AE-0A76-9AC4FC01C199}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_opengl\geometry">
<UniqueIdentifier>{1A9221A3-E993-70B2-6EA2-8E1DB5FF646A}</UniqueIdentifier>
</Filter>
@ -3196,6 +3205,42 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.mm">
<Filter>JUCE Modules\juce_gui_extra</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.cpp">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.cpp">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.cpp">
<Filter>JUCE Modules\juce_midi_ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_opengl\opengl\juce_gl.cpp">
<Filter>JUCE Modules\juce_opengl\opengl</Filter>
</ClCompile>
@ -3334,6 +3379,9 @@
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_midi_ci.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_opengl.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
@ -3387,12 +3435,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
@ -6339,6 +6393,99 @@
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.h">
<Filter>JUCE Modules\juce_gui_extra</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIChannelAddress.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceFeatures.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceListener.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceMessageHandler.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceOptions.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncoding.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIFunctionBlock.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMessages.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMuid.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileAtAddress.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeResult.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISubscription.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISupportedAndActive.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMarshalling.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageMeta.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageTypeUtils.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyHostUtils.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.h">
<Filter>JUCE Modules\juce_midi_ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Draggable3DOrientation.h">
<Filter>JUCE Modules\juce_opengl\geometry</Filter>
</ClInclude>

View file

@ -64,7 +64,7 @@
<Optimization>Disabled</Optimization>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
@ -80,7 +80,7 @@
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<OutputFile>$(OutDir)\UnitTestRunner.exe</OutputFile>
@ -108,7 +108,7 @@
<ClCompile>
<Optimization>Full</Optimization>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
@ -124,7 +124,7 @@
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\..\..\modules\juce_audio_processors\format_types\LV2_SDK;..\..\..\..\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=0;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70008;JUCE_MODULE_AVAILABLE_juce_analytics=1;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_cryptography=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_dsp=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_MODULE_AVAILABLE_juce_midi_ci=1;JUCE_MODULE_AVAILABLE_juce_opengl=1;JUCE_MODULE_AVAILABLE_juce_osc=1;JUCE_MODULE_AVAILABLE_juce_product_unlocking=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_PLUGINHOST_VST3=1;JUCE_PLUGINHOST_LV2=1;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_STANDALONE_APPLICATION=1;JUCE_UNIT_TESTS=1;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<OutputFile>$(OutDir)\UnitTestRunner.exe</OutputFile>
@ -2525,6 +2525,42 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_opengl\opengl\juce_gl.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
@ -2631,6 +2667,7 @@
<AdditionalOptions> /bigobj %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_midi_ci.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_opengl.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_osc.cpp"/>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_product_unlocking.cpp"/>
@ -2650,8 +2687,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>
@ -3634,6 +3673,37 @@
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\misc\juce_WebBrowserComponent.h"/>
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\native\juce_NSViewFrameWatcher_mac.h"/>
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIChannelAddress.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceFeatures.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceListener.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceMessageHandler.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceOptions.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncoding.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIFunctionBlock.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMessages.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMuid.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileAtAddress.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeResult.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderDelegate.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISubscription.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISupportedAndActive.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMarshalling.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageMeta.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageTypeUtils.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyHostUtils.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.h"/>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Draggable3DOrientation.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Matrix3D.h"/>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Quaternion.h"/>

View file

@ -623,6 +623,15 @@
<Filter Include="JUCE Modules\juce_gui_extra">
<UniqueIdentifier>{A4D76113-9EDC-DA60-D89B-5BACF7F1C426}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci\ci">
<UniqueIdentifier>{EC0A49B5-F336-1F4D-6C32-40E19BE1426F}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci\detail">
<UniqueIdentifier>{39CDBE58-7B8F-B367-DAE9-BCA326A4C637}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_midi_ci">
<UniqueIdentifier>{F64F0BED-92DA-A4AE-0A76-9AC4FC01C199}</UniqueIdentifier>
</Filter>
<Filter Include="JUCE Modules\juce_opengl\geometry">
<UniqueIdentifier>{1A9221A3-E993-70B2-6EA2-8E1DB5FF646A}</UniqueIdentifier>
</Filter>
@ -3196,6 +3205,42 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.mm">
<Filter>JUCE Modules\juce_gui_extra</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.cpp">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.cpp">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.cpp">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.cpp">
<Filter>JUCE Modules\juce_midi_ci</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_opengl\opengl\juce_gl.cpp">
<Filter>JUCE Modules\juce_opengl\opengl</Filter>
</ClCompile>
@ -3334,6 +3379,9 @@
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_extra.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_midi_ci.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
<ClCompile Include="..\..\JuceLibraryCode\include_juce_opengl.cpp">
<Filter>JUCE Library Code</Filter>
</ClCompile>
@ -3387,12 +3435,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
@ -6339,6 +6393,99 @@
<ClInclude Include="..\..\..\..\modules\juce_gui_extra\juce_gui_extra.h">
<Filter>JUCE Modules\juce_gui_extra</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIChannelAddress.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDevice.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceFeatures.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceListener.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceMessageHandler.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIDeviceOptions.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncoding.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIEncodings.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIFunctionBlock.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMessages.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIMuid.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIParser.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileAtAddress.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileHost.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIProfileStates.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeCache.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyExchangeResult.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIPropertyHost.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderDelegate.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CIResponderOutput.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISubscription.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\ci\juce_CISupportedAndActive.h">
<Filter>JUCE Modules\juce_midi_ci\ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMarshalling.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageMeta.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIMessageTypeUtils.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyDataMessageChunker.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIPropertyHostUtils.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\detail\juce_CIResponder.h">
<Filter>JUCE Modules\juce_midi_ci\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_midi_ci\juce_midi_ci.h">
<Filter>JUCE Modules\juce_midi_ci</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_opengl\geometry\juce_Draggable3DOrientation.h">
<Filter>JUCE Modules\juce_opengl\geometry</Filter>
</ClInclude>

View file

@ -42,6 +42,7 @@ target_link_libraries(UnitTestRunner PRIVATE
juce::juce_analytics
juce::juce_audio_utils
juce::juce_dsp
juce::juce_midi_ci
juce::juce_opengl
juce::juce_osc
juce::juce_product_unlocking

View file

@ -27,6 +27,7 @@
#include <juce_graphics/juce_graphics.h>
#include <juce_gui_basics/juce_gui_basics.h>
#include <juce_gui_extra/juce_gui_extra.h>
#include <juce_midi_ci/juce_midi_ci.h>
#include <juce_opengl/juce_opengl.h>
#include <juce_osc/juce_osc.h>
#include <juce_product_unlocking/juce_product_unlocking.h>

View file

@ -0,0 +1,8 @@
/*
IMPORTANT! This file is auto-generated each time you save your
project - if you alter its contents, your changes may be overwritten!
*/
#include <juce_midi_ci/juce_midi_ci.cpp>

View file

@ -34,6 +34,7 @@
<MODULEPATH id="juce_audio_utils" path="../../modules"/>
<MODULEPATH id="juce_product_unlocking" path="../../modules"/>
<MODULEPATH id="juce_analytics" path="../../modules"/>
<MODULEPATH id="juce_midi_ci" path="../../modules"/>
</MODULEPATHS>
</XCODE_MAC>
<LINUX_MAKE targetFolder="Builds/LinuxMakefile">
@ -59,6 +60,7 @@
<MODULEPATH id="juce_audio_utils" path="../../modules"/>
<MODULEPATH id="juce_product_unlocking" path="../../modules"/>
<MODULEPATH id="juce_analytics" path="../../modules"/>
<MODULEPATH id="juce_midi_ci" path="../../modules"/>
</MODULEPATHS>
</LINUX_MAKE>
<VS2017 targetFolder="Builds/VisualStudio2017" extraCompilerFlags="/w44265 /w45038 /w44062">
@ -85,6 +87,7 @@
<MODULEPATH id="juce_audio_devices" path="../../modules"/>
<MODULEPATH id="juce_audio_basics" path="../../modules"/>
<MODULEPATH id="juce_analytics" path="../../modules"/>
<MODULEPATH id="juce_midi_ci" path="../../modules"/>
</MODULEPATHS>
</VS2017>
<VS2019 targetFolder="Builds/VisualStudio2019" extraCompilerFlags="/w44265 /w45038 /w44062">
@ -111,6 +114,7 @@
<MODULEPATH id="juce_audio_devices" path="../../modules"/>
<MODULEPATH id="juce_audio_basics" path="../../modules"/>
<MODULEPATH id="juce_analytics" path="../../modules"/>
<MODULEPATH id="juce_midi_ci" path="../../modules"/>
</MODULEPATHS>
</VS2019>
<VS2022 targetFolder="Builds/VisualStudio2022" extraCompilerFlags="/w44265 /w45038 /w44062">
@ -137,6 +141,7 @@
<MODULEPATH id="juce_audio_devices" path="../../modules"/>
<MODULEPATH id="juce_audio_basics" path="../../modules"/>
<MODULEPATH id="juce_analytics" path="../../modules"/>
<MODULEPATH id="juce_midi_ci" path="../../modules"/>
</MODULEPATHS>
</VS2022>
</EXPORTFORMATS>
@ -155,6 +160,7 @@
<MODULES id="juce_graphics" showAllCode="1" useLocalCopy="0"/>
<MODULES id="juce_gui_basics" showAllCode="1" useLocalCopy="0"/>
<MODULES id="juce_gui_extra" showAllCode="1" useLocalCopy="0"/>
<MODULE id="juce_midi_ci" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
<MODULES id="juce_opengl" showAllCode="1" useLocalCopy="0"/>
<MODULES id="juce_osc" showAllCode="1" useLocalCopy="0"/>
<MODULE id="juce_product_unlocking" showAllCode="1" useLocalCopy="0"

View file

@ -2481,8 +2481,10 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMP.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPacket.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPFactory.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPIterator.h"/>

View file

@ -3156,12 +3156,18 @@
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPackets.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPBytesOnGroup.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConversion.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPConverters.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDeviceInfo.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\midi\ump\juce_UMPDispatcher.h">
<Filter>JUCE Modules\juce_audio_basics\midi\ump</Filter>
</ClInclude>

View file

@ -40,6 +40,7 @@ juce_add_modules(
juce_graphics
juce_gui_basics
juce_gui_extra
juce_midi_ci
juce_opengl
juce_osc
juce_product_unlocking

View file

@ -124,3 +124,10 @@ JUCE_END_IGNORE_WARNINGS_MSVC
#include "synthesisers/juce_Synthesiser.h"
#include "audio_play_head/juce_AudioPlayHead.h"
#include "utilities/juce_AudioWorkgroup.h"
#include "midi/ump/juce_UMPBytesOnGroup.h"
#include "midi/ump/juce_UMPDeviceInfo.h"
namespace juce
{
namespace ump = universal_midi_packets;
}

View file

@ -685,6 +685,11 @@ MidiMessage MidiMessage::createSysExMessage (const void* sysexData, const int da
return MidiMessage (m, dataSize + 2);
}
MidiMessage MidiMessage::createSysExMessage (Span<const std::byte> data)
{
return createSysExMessage (data.data(), (int) data.size());
}
const uint8* MidiMessage::getSysExData() const noexcept
{
return isSysEx() ? getRawData() + 1 : nullptr;

View file

@ -218,6 +218,13 @@ public:
*/
int getSysExDataSize() const noexcept;
/** Returns a span that bounds the sysex body bytes contained in this message. */
Span<const std::byte> getSysExDataSpan() const noexcept
{
return { reinterpret_cast<const std::byte*> (getSysExData()),
(size_t) getSysExDataSize() };
}
//==============================================================================
/** Returns true if this message is a 'key-down' event.
@ -855,6 +862,10 @@ public:
static MidiMessage createSysExMessage (const void* sysexData,
int dataSize);
/** Creates a system-exclusive message.
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7.
*/
static MidiMessage createSysExMessage (Span<const std::byte> data);
//==============================================================================
#ifndef DOXYGEN

View file

@ -0,0 +1,38 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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::universal_midi_packets
{
/**
Holds a UMP group, and a span of bytes that were received or are to be
sent on that group. Helpful when working with sysex messages.
@tags{Audio}
*/
struct BytesOnGroup
{
uint8_t group{};
Span<const std::byte> bytes;
};
} // namespace juce::universal_midi_packets

View file

@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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::universal_midi_packets
{
/**
Holds MIDI device info that may be required by certain UMP messages and
MIDI-CI messages.
@tags{Audio}
*/
struct DeviceInfo
{
std::array<std::byte, 3> manufacturer; ///< LSB first
std::array<std::byte, 2> family; ///< LSB first
std::array<std::byte, 2> modelNumber; ///< LSB first
std::array<std::byte, 4> revision;
private:
auto tie() const { return std::tie (manufacturer, family, modelNumber, revision); }
public:
bool operator== (const DeviceInfo& other) const { return tie() == other.tie(); }
bool operator!= (const DeviceInfo& other) const { return tie() != other.tie(); }
static constexpr auto marshallingVersion = std::nullopt;
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
return archive (named ("manufacturer", t.manufacturer),
named ("family", t.family),
named ("modelNumber", t.modelNumber),
named ("revision", t.revision));
}
};
} // namespace juce::universal_midi_packets

View file

@ -0,0 +1,83 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Identifies a channel or set of channels in a multi-group MIDI endpoint.
@tags{Audio}
*/
class ChannelAddress
{
private:
uint8_t group{}; ///< A group within a MIDI endpoint, where 0 <= group && group < 16
ChannelInGroup channel{}; ///< A set of channels related to specified group
auto tie() const { return std::tie (group, channel); }
public:
/** Returns a copy of this object with the specified group. */
[[nodiscard]] ChannelAddress withGroup (int g) const
{
jassert (isPositiveAndBelow (g, 16));
return withMember (*this, &ChannelAddress::group, (uint8_t) g);
}
/** Returns a copy of this object with the specified channel. */
[[nodiscard]] ChannelAddress withChannel (ChannelInGroup c) const
{
return withMember (*this, &ChannelAddress::channel, c);
}
/** Returns the group. */
[[nodiscard]] uint8_t getGroup() const { return group; }
/** Returns the channel in the group. */
[[nodiscard]] ChannelInGroup getChannel() const { return channel; }
/** Returns true if this address refers to all channels in the function
block containing the specified group.
*/
bool isBlock() const { return channel == ChannelInGroup::wholeBlock; }
/** Returns true if this address refers to all channels in the specified
group.
*/
bool isGroup() const { return channel == ChannelInGroup::wholeGroup; }
/** Returns true if this address refers to a single channel. */
bool isSingleChannel() const { return ! isBlock() && ! isGroup(); }
bool operator< (const ChannelAddress& other) const { return tie() < other.tie(); }
bool operator<= (const ChannelAddress& other) const { return tie() <= other.tie(); }
bool operator> (const ChannelAddress& other) const { return tie() > other.tie(); }
bool operator>= (const ChannelAddress& other) const { return tie() >= other.tie(); }
bool operator== (const ChannelAddress& other) const { return tie() == other.tie(); }
bool operator!= (const ChannelAddress& other) const { return ! operator== (other); }
};
} // namespace juce::midi_ci

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,319 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
//==============================================================================
/**
Instances of this type are responsible for parsing and interpreting incoming
MIDI-CI messages, and for sending MIDI-CI messages to other devices.
Each Device can act both as a target for messages, and as a source of
messages intended to inspect/configure other devices.
The member functions of Device are generally used to inspect other
devices. Member functions starting with 'send' are used to send or request
information from other devices; registered DeviceListeners will be notified
when the Device receives a response, and then member functions named
matching 'get.*ForMuid' can be used to retrieve the result of the inquiry.
If the Device does not have local profiles or properties, then responses
to all incoming messages will be generated automatically using the
information supplied during construction.
If the Device has profiles or properties, then you should implement a
ProfileDelegate and/or a PropertyDelegate as appropriate, and pass this
delegate during construction. Each Delegate will receive callbacks when a
remote device makes a request of the local device, such as
enabling/disabling a profile, or setting/getting property data.
Sometimes the local device must send notifications when
updating its profile or property state, for example when profiles are
added, or when a subscribed property is changed. Methods to send these
notifications are found on the ProfileHost and PropertyHost classes.
@tags{Audio}
*/
class Device : public DeviceMessageHandler
{
public:
using Features = DeviceFeatures;
using Listener = DeviceListener;
using Options = DeviceOptions;
/** Constructs a device using the provided options. */
explicit Device (const Options& opt);
Device (Device&&) noexcept;
Device& operator= (Device&&) noexcept;
JUCE_DECLARE_NON_COPYABLE (Device)
/** Destructor, sends a message to invalidate this device's MUID. */
~Device() override;
//==============================================================================
/** To be called with any message that should be processed by the device.
This should only be passed complete CI messages - you might find the Extractor
class useful for parsing a stream of Universal MIDI Packets and extracting the
CI messages.
Note that this function does *not* synchronise with any other member function of this
class. This means that you must not call this directly from the MIDI input thread if there's
any chance of other member functions being called on the same instance simultaneously from
other threads.
It's probably easiest to send all messages onto the main thread and to limit interactions
with the Device to that thread.
*/
void processMessage (ump::BytesOnGroup) override;
//==============================================================================
/** Sends an inquiry message.
You can use DeviceListener::deviceAdded to be notified when new devices are discovered.
This will clear the internal cache of discovered devices, and repopulate it as discovery
response messages are received.
*/
void sendDiscovery();
/** Sends an endpoint inquiry message.
Check the MIDI-CI spec for an explanation of the different endpoint message status codes.
Received responses will be sent to DeviceListener::endpointReceived. Responses are not
cached by the Device; if you need to cache endpoint responses, you can keep your own
map of MUID->response, update it in endpointReceived, and remove entries in
DeviceListener::deviceRemoved.
*/
void sendEndpointInquiry (MUID destination, Message::EndpointInquiry endpoint);
//==============================================================================
/** Sends a profile inquiry to a particular device.
DeviceListener::profileStateReceived will be called when the device replies.
*/
void sendProfileInquiry (MUID muid, ChannelInGroup address);
/** Sends a profile details inquiry to a particular device.
DeviceListener::profileDetailsReceived will be called when the device replies.
*/
void sendProfileDetailsInquiry (MUID muid, ChannelInGroup address, Profile profile, std::byte target);
/** Sends profile data to a particular device. */
void sendProfileSpecificData (MUID muid, ChannelInGroup address, Profile profile, Span<const std::byte>);
/** Sets a profile on or off. Pass 0 or less to disable the profile, or a positive number to enable it.
*/
void sendProfileEnablement (MUID muid, ChannelInGroup address, Profile profile, int numChannels);
//==============================================================================
/** Sends a property inquiry to a particular device.
If the device supports properties, this will also automatically request the ResourceList
property, and then the ChannelList and DeviceInfo properties if they are present in the
ResourceList.
*/
void sendPropertyCapabilitiesInquiry (MUID destination);
/** Sends an inquiry to get a property value from another device, invoking a callback once
the full transaction has completed.
@param destination the device whose property will be set
@param header information about the property data that will be sent
@param onResult this will be called once the result of the transaction is known.
If the transaction cannot start for some reason (e.g. the request is
malformed, or there are too many simultaneous requests) then the
function will be called immediately. Otherwise, the function will be
called once the destination device has confirmed receipt of the
inquiry.
@return a token bounding the lifetime of the request.
If you need to terminate the transaction before it has completed,
you can call reset() on this token, or cause its destructor to run.
*/
ErasedScopeGuard sendPropertyGetInquiry (MUID destination,
const PropertyRequestHeader& header,
std::function<void (const PropertyExchangeResult&)> onResult);
/** Sends an inquiry to set a property value on another device, invoking a callback once
the full transaction has completed.
@param destination the device whose property will be set
@param header information about the property data that will be sent
@param body the property data payload to send.
If the header specifies 'ascii' encoding, then you are responsible
for ensuring that no byte of the payload data has its most
significant bit set. Sending the message will fail if this is not
the case. Otherwise, if another encoding is specified then the
payload data may contain any byte values. You should not attempt to
encode the data yourself; the payload will be automatically encoded
before being sent.
@param onResult this will be called once the result of the transaction is known.
If the transaction cannot start for some reason (e.g. the
destination does not support property exchange, the request is
malformed, or there are too many simultaneous requests) then the
function will be called immediately. Otherwise, the function will be
called once the destination device has confirmed receipt of the
inquiry.
*/
void sendPropertySetInquiry (MUID destination,
const PropertyRequestHeader& header,
Span<const std::byte> body,
std::function<void (const PropertyExchangeResult&)> onResult);
/** Sends an inquiry to start a subscription to a property on a device.
The provided callback will be called to indicate whether starting the subscription
succeeded or failed.
When the remote device indicates that its property value has changed,
DeviceListener::propertySubscriptionReceived will be called with information about the
update.
*/
void sendPropertySubscriptionStart (MUID,
const PropertySubscriptionHeader& header,
std::function<void (const PropertyExchangeResult&)>);
/** Sends an inquiry to end a subscription to a property on a device.
The provided callback will be called to indicate whether the subscriber acknowledged
receipt of the message.
Note that the remote device may also choose to terminate the subscription of its own
accord - in this case, the end request will be sent to
DeviceListener::propertySubscriptionReceived.
*/
void sendPropertySubscriptionEnd (MUID,
const String& subscribeId,
std::function<void (const PropertyExchangeResult&)>);
/** Returns all of the subscriptions that we have requested from another device.
Does *not* include subscriptions that other devices have requested from us.
*/
std::vector<Subscription> getOngoingSubscriptionsForMuid (MUID m) const;
/** Returns the number of transactions initiated by us that are yet to receive complete replies.
Does *not* include the count of unfinished requests addressed to us by other devices.
@see PropertyHost::countOngoingTransactions()
*/
int countOngoingPropertyTransactions() const;
//==============================================================================
/** Adds a listener that will be notified when particular events occur.
Check the members of the Listener class to see the kinds of events that are reported.
To receive notifications through Listener::propertySubscriptionReceived(), you must
first request a subscription using sendPropertySubscriptionStart().
@see Listener, removeListener()
*/
void addListener (Listener& l);
/** Removes a listener that was previously added with addListener(). */
void removeListener (Listener& l);
//==============================================================================
/** Returns the MUID currently associated with this device.
This may change, e.g. if another device reports that it shares the same MUID.
*/
MUID getMuid() const;
/** Returns the configuration of this device. */
Options getOptions() const;
/** Returns a list of all MUIDs that have been discovered by this device. */
std::vector<MUID> getDiscoveredMuids() const;
/** If you set withProfileConfigurationSupported when constructing this device, this will return
a pointer to an object that can be used to query the states of the profiles for this device.
*/
const ProfileHost* getProfileHost() const;
/** If you set withProfileConfigurationSupported when constructing this device, this will return
a pointer to an object that can be used to modify the states of the profiles for this device.
*/
ProfileHost* getProfileHost();
/** If you set withPropertyExchangeSupported when constructing this device, this will return
a pointer to an object that can be used to query the states of the properties for this device.
*/
const PropertyHost* getPropertyHost() const;
/** If you set withPropertyExchangeSupported when constructing this device, this will return
a pointer to an object that can be used to modify the states of the properties for this device.
*/
PropertyHost* getPropertyHost();
//==============================================================================
/** Returns basic attributes about another device that was discovered.
If there's no record of the provided device, this will return nullopt.
*/
std::optional<Message::Discovery> getDiscoveryInfoForMuid (MUID m) const;
/** Returns the states of the profiles on a particular channel of a device.
If the state is unknown, returns nullptr.
Devices don't report profile capabilities unless asked; you can request capabilities
using inquireProfile().
*/
const ChannelProfileStates* getProfileStateForMuid (MUID m, ChannelAddress address) const;
/** Returns the number of simultaneous property exchange requests supported by a particular
device.
If there's no record of this device's property capabilities (including the case where
the device doesn't support property exchange at all) this will return nullopt.
Devices don't report property capabilities unless asked; you can request capabilities
using inquirePropertyCapabilities().
*/
std::optional<int> getNumPropertyExchangeRequestsSupportedForMuid (MUID m) const;
/** After DeviceListener::propertyExchangeCapabilitiesReceived() has been received for a
particular device, this function will return that device's ResourceList if available, or
a null var otherwise.
*/
var getResourceListForMuid (MUID x) const;
/** After DeviceListener::propertyExchangeCapabilitiesReceived() has been received for a
particular device, this function will return that device's DeviceInfo if available, or
a null var otherwise.
*/
var getDeviceInfoForMuid (MUID x) const;
/** After DeviceListener::propertyExchangeCapabilitiesReceived() has been received for a
particular device, this function will return that device's ChannelList if available, or
a null var otherwise.
*/
var getChannelListForMuid (MUID x) const;
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,90 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Flags indicating the features that are supported by a given CI device.
@tags{Audio}
*/
class DeviceFeatures
{
public:
/** Constructs a DeviceFeatures object with no flags enabled. */
DeviceFeatures() = default;
/** Constructs a DeviceFeatures object, taking flag values from the "Capability Inquiry
Category Supported" byte in a CI Discovery message.
*/
explicit DeviceFeatures (std::byte f) : flags ((uint8_t) f) {}
/** Returns a new DeviceFeatures instance with profile configuration marked as supported. */
[[nodiscard]] DeviceFeatures withProfileConfigurationSupported (bool x = true) const { return withFlag (profileConfiguration, x); }
/** Returns a new DeviceFeatures instance with property exchange marked as supported. */
[[nodiscard]] DeviceFeatures withPropertyExchangeSupported (bool x = true) const { return withFlag (propertyExchange, x); }
/** Returns a new DeviceFeatures instance with process inquiry marked as supported. */
[[nodiscard]] DeviceFeatures withProcessInquirySupported (bool x = true) const { return withFlag (processInquiry, x); }
/** @see withProfileConfigurationSupported() */
[[nodiscard]] bool isProfileConfigurationSupported () const { return getFlag (profileConfiguration); }
/** @see withPropertyExchangeSupported() */
[[nodiscard]] bool isPropertyExchangeSupported () const { return getFlag (propertyExchange); }
/** @see withProcessInquirySupported() */
[[nodiscard]] bool isProcessInquirySupported () const { return getFlag (processInquiry); }
/** Returns the feature flags formatted into a bitfield suitable for use as the "Capability
Inquiry Category Supported" byte in a CI Discovery message.
*/
std::byte getSupportedCapabilities() const { return std::byte { flags }; }
/** Returns true if this and other both have the same flags set. */
bool operator== (const DeviceFeatures& other) const { return flags == other.flags; }
/** Returns true if any flags in this and other differ. */
bool operator!= (const DeviceFeatures& other) const { return ! operator== (other); }
private:
enum Flags
{
profileConfiguration = 1 << 2,
propertyExchange = 1 << 3,
processInquiry = 1 << 4,
};
[[nodiscard]] DeviceFeatures withFlag (Flags f, bool value) const
{
return withMember (*this, &DeviceFeatures::flags, (uint8_t) (value ? (flags | f) : (flags & ~f)));
}
bool getFlag (Flags f) const
{
return (flags & f) != 0;
}
uint8_t flags = 0;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,146 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Contains information relating to a subscription update. Check the header's
subscription kind to find out whether the payload is a full update, a
partial update, or empty (as is the case for a notification or
subscription-end request).
@tags{Audio}
*/
struct PropertySubscriptionData
{
PropertySubscriptionHeader header;
Span<const std::byte> body;
};
//==============================================================================
/**
An interface that receives callbacks when certain messages are received by a Device.
@tags{Audio}
*/
struct DeviceListener
{
DeviceListener() = default;
virtual ~DeviceListener() = default;
DeviceListener (const DeviceListener&) = default;
DeviceListener (DeviceListener&&) = default;
DeviceListener& operator= (const DeviceListener&) = default;
DeviceListener& operator= (DeviceListener&&) = default;
//==============================================================================
/** Called to indicate that a device with the provided MUID was discovered.
To find out more about the device, use Device::getDiscoveryInfoForMuid().
*/
virtual void deviceAdded ([[maybe_unused]] MUID x) {}
/** Called to indicate that a device's MUID was invalidated.
If you were previously storing your own information about this device, you should forget
that information here.
*/
virtual void deviceRemoved ([[maybe_unused]] MUID x) {}
/** Called to indicate that endpoint information was received for the given device.
See the MIDI-CI spec for an explanation of the different status codes.
*/
virtual void endpointReceived ([[maybe_unused]] MUID x,
[[maybe_unused]] Message::EndpointInquiryResponse response) {}
/** Called to indicate that a NAK message was received.
This is useful e.g. to display a diagnostic to the user, or to cache the failed request
details and retry the request at a later date.
The message field of the NAK is 7-bit text. You can convert it to a string using
Encodings::stringFrom7BitText().
*/
virtual void messageNotAcknowledged ([[maybe_unused]] MUID x,
[[maybe_unused]] Message::NAK) {}
//==============================================================================
/** Called to indicate that another device reported its enabled and disabled profiles on a
particular channel.
@see Device::getProfileStateForMuid()
*/
virtual void profileStateReceived ([[maybe_unused]] MUID x,
[[maybe_unused]] ChannelInGroup destination) {}
/** Called to indicate that a profile was added or removed. */
virtual void profilePresenceChanged ([[maybe_unused]] MUID x,
[[maybe_unused]] ChannelInGroup destination,
[[maybe_unused]] Profile profile,
[[maybe_unused]] bool exists) {}
/** Called to indicate that a profile was enabled or disabled.
A channel count of 0 indicates that the profile was disabled.
*/
virtual void profileEnablementChanged ([[maybe_unused]] MUID x,
[[maybe_unused]] ChannelInGroup destination,
[[maybe_unused]] Profile profile,
[[maybe_unused]] int numChannels) {}
/** Called to indicate that details about a profile were received. */
virtual void profileDetailsReceived ([[maybe_unused]] MUID x,
[[maybe_unused]] ChannelInGroup destination,
[[maybe_unused]] Profile profile,
[[maybe_unused]] std::byte target,
[[maybe_unused]] Span<const std::byte> data) {}
/** Called to indicate that data for a profile were received.
Note that this function may be called either when a remote device attempts to send data to
one of the local Device's profiles, or when a profile on a remote device produces some data.
Each profile will specify its own mechanism for distinguishing between the two cases if
necessary.
*/
virtual void profileSpecificDataReceived ([[maybe_unused]] MUID x,
[[maybe_unused]] ChannelInGroup destination,
[[maybe_unused]] Profile profile,
[[maybe_unused]] Span<const std::byte> data) {}
//==============================================================================
/** Called to indicate that another device reported its property exchange capabilities.
@see Device::getPropertyExchangeCapabilitiesResponseForMuid()
*/
virtual void propertyExchangeCapabilitiesReceived ([[maybe_unused]] MUID x) {}
/** Called to indicate that a subscription update was received.
This only receives messages with responder commands (partial, full, notify, end).
To start a subscription, use Device::sendPropertySubscriptionStart().
*/
virtual void propertySubscriptionDataReceived ([[maybe_unused]] MUID x,
[[maybe_unused]] const PropertySubscriptionData& data) {}
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,53 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
//==============================================================================
/**
An interface that will receive a callback every time a Device wishes to send a new MIDI-CI
message.
@tags{Audio}
*/
struct DeviceMessageHandler
{
DeviceMessageHandler() = default;
virtual ~DeviceMessageHandler() = default;
DeviceMessageHandler (const DeviceMessageHandler&) = default;
DeviceMessageHandler (DeviceMessageHandler&&) = default;
DeviceMessageHandler& operator= (const DeviceMessageHandler&) = default;
DeviceMessageHandler& operator= (DeviceMessageHandler&&) = default;
/** Called with the bytes of a MIDI-CI message, along with the message's group.
To send the message on, format the message appropriately (either into bytestream sysex
or into multiple UMP sysex packets).
*/
virtual void processMessage (ump::BytesOnGroup) = 0;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,167 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
//==============================================================================
/**
Configuration options for a Device.
The options set here will remain constant over the lifetime of a Device.
@tags{Audio}
*/
class DeviceOptions
{
public:
static constexpr auto beginValidAscii = 32; // inclusive
static constexpr auto endValidAscii = 127; // exclusive
/** Creates a random product instance ID.
This isn't really recommended - it's probably better to have a unique ID that remains
persistent after a restart.
*/
static std::array<char, 16> makeProductInstanceId (Random& random)
{
std::array<char, 16> result{};
for (auto& c : result)
c = (char) random.nextInt ({ beginValidAscii, endValidAscii });
return result;
}
/** One or more DeviceMessageHandlers that should receive callbacks with any messages that the
device wishes to send.
Referenced DeviceMessageHandlers *must* outlive any Device constructed from these options.
*/
[[nodiscard]] DeviceOptions withOutputs (std::vector<DeviceMessageHandler*> x) const
{
return withMember (*this, &DeviceOptions::outputs, x);
}
/** The function block layout of this device. */
[[nodiscard]] DeviceOptions withFunctionBlock (FunctionBlock x) const
{
return withMember (*this, &DeviceOptions::functionBlock, x);
}
/** Basic information about the device used to determine manufacturer, model, etc.
In order to populate this correctly, you'll need to register with the MIDI association -
otherwise you might accidentally end up using IDs that are already assigned to other
companies/individuals.
*/
[[nodiscard]] DeviceOptions withDeviceInfo (const ump::DeviceInfo& x) const
{
return withMember (*this, &DeviceOptions::deviceInfo, x);
}
/** The features that you want to enable on the device.
If you enable property exchange, you may wish to supply a PropertyDelegate using
withPropertyDelegate().
If you enable profile configuration, you may wish to supply a ProfileDelegate using
withProfileDelegate().
Process inquiry is not currently supported.
*/
[[nodiscard]] DeviceOptions withFeatures (DeviceFeatures x) const
{
return withMember (*this, &DeviceOptions::features, x);
}
/** The maximum size of sysex messages to accept and to produce. */
[[nodiscard]] DeviceOptions withMaxSysExSize (size_t x) const
{
return withMember (*this, &DeviceOptions::maxSysExSize, x);
}
/** Specifies a profile delegate that can be used to respond to particular profile events.
The referenced ProfileDelegate *must* outlive the Device.
*/
[[nodiscard]] DeviceOptions withProfileDelegate (ProfileDelegate* x) const
{
return withMember (*this, &DeviceOptions::profileDelegate, x);
}
/** Specifies a property delegate that can be used to respond to particular property events.
The referenced PropertyDelegate *must* outlive the Device.
*/
[[nodiscard]] DeviceOptions withPropertyDelegate (PropertyDelegate* x) const
{
return withMember (*this, &DeviceOptions::propertyDelegate, x);
}
/** Specifies a product instance ID that will be returned in endpoint response messages. */
[[nodiscard]] DeviceOptions withProductInstanceId (const std::array<char, 16>& x) const
{
const auto null = std::find (x.begin(), x.end(), 0);
if (! std::all_of (x.begin(), null, [] (char c) { return beginValidAscii <= c && c < endValidAscii; }))
{
// The product instance ID must be made up of ASCII characters
jassertfalse;
return *this;
}
if (std::any_of (null, x.end(), [] (auto c) { return c != 0; }))
{
// All characters after the null terminator must be 0
jassertfalse;
return *this;
}
return withMember (*this, &DeviceOptions::productInstanceId, x);
}
/** @see withOutputs() */
[[nodiscard]] const auto& getOutputs() const { return outputs; }
/** @see withFunctionBlock() */
[[nodiscard]] const auto& getFunctionBlock() const { return functionBlock; }
/** @see withDeviceInfo() */
[[nodiscard]] const auto& getDeviceInfo() const { return deviceInfo; }
/** @see withFeatures() */
[[nodiscard]] const auto& getFeatures() const { return features; }
/** @see withMaxSysExSize() */
[[nodiscard]] const auto& getMaxSysExSize() const { return maxSysExSize; }
/** @see withProductInstanceId() */
[[nodiscard]] const auto& getProductInstanceId() const { return productInstanceId; }
/** @see withProfileDelegate() */
[[nodiscard]] const auto& getProfileDelegate() const { return profileDelegate; }
/** @see withPropertyDelegate() */
[[nodiscard]] const auto& getPropertyDelegate() const { return propertyDelegate; }
private:
std::vector<DeviceMessageHandler*> outputs;
FunctionBlock functionBlock;
ump::DeviceInfo deviceInfo;
DeviceFeatures features;
size_t maxSysExSize = 512;
std::array<char, 16> productInstanceId{};
ProfileDelegate* profileDelegate = nullptr;
PropertyDelegate* propertyDelegate = nullptr;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,104 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
#define JUCE_ENCODINGS X(ascii, "ASCII") X(mcoded7, "Mcoded7") X(zlibAndMcoded7, "zlib+Mcoded7")
/**
Identifies different encodings that may be used by property exchange messages.
@tags{Audio}
*/
enum class Encoding
{
#define X(name, unused) name,
JUCE_ENCODINGS
#undef X
};
/**
Utility functions for working with the Encoding enum.
@tags{Audio}
*/
struct EncodingUtils
{
EncodingUtils() = delete;
/** Converts an Encoding to a human-readable string. */
static const char* toString (Encoding e)
{
switch (e)
{
#define X(name, string) case Encoding::name: return string;
JUCE_ENCODINGS
#undef X
}
return nullptr;
}
/** Converts an encoding string from a property exchange JSON header to
an Encoding.
*/
static std::optional<Encoding> toEncoding (const char* str)
{
#define X(name, string) if (std::string_view (str) == std::string_view (string)) return Encoding::name;
JUCE_ENCODINGS
#undef X
return {};
}
};
#undef JUCE_ENCODINGS
} // namespace juce::midi_ci
namespace juce
{
template <>
struct SerialisationTraits<midi_ci::Encoding>
{
static constexpr auto marshallingVersion = std::nullopt;
template <typename Archive>
void load (Archive& archive, midi_ci::Encoding& t)
{
String encoding;
archive (encoding);
t = midi_ci::EncodingUtils::toEncoding (encoding.toRawUTF8()).value_or (midi_ci::Encoding{});
}
template <typename Archive>
void save (Archive& archive, const midi_ci::Encoding& t)
{
archive (midi_ci::EncodingUtils::toString (t));
}
};
} // namespace juce

View file

@ -0,0 +1,379 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
String Encodings::stringFrom7BitText (Span<const std::byte> bytes)
{
std::vector<CharPointer_UTF16::CharType> chars;
while (! bytes.empty())
{
const auto front = (uint8_t) bytes.front();
if ((front < 0x20 || 0x80 <= front) && front != 0x0a)
{
jassertfalse;
return {};
}
if (front == '\\')
{
bytes = Span (bytes.data() + 1, bytes.size() - 1);
if (bytes.empty())
return {};
const auto kind = (uint8_t) bytes.front();
switch (kind)
{
case '"': chars.push_back ('"'); break;
case '\\': chars.push_back ('\\'); break;
case '/': chars.push_back ('/'); break;
case 'b': chars.push_back ('\b'); break;
case 'f': chars.push_back ('\f'); break;
case 'n': chars.push_back ('\n'); break;
case 'r': chars.push_back ('\r'); break;
case 't': chars.push_back ('\t'); break;
case 'u':
{
bytes = Span (bytes.data() + 1, bytes.size() - 1);
if (bytes.size() < 4)
return {};
std::string byteStr (reinterpret_cast<const char*> (bytes.data()), 4);
const auto unit = [&]() -> std::optional<CharPointer_UTF16::CharType>
{
try
{
return (CharPointer_UTF16::CharType) std::stoi (byteStr, {}, 16);
}
catch (...) {}
jassertfalse;
return {};
}();
if (! unit.has_value())
return {};
chars.push_back (*unit);
bytes = Span (bytes.data() + 4, bytes.size() - 4);
continue;
}
default:
return {};
}
bytes = Span (bytes.data() + 1, bytes.size() - 1);
}
else
{
chars.push_back (front);
bytes = Span (bytes.data() + 1, bytes.size() - 1);
}
}
chars.push_back ({});
return String { CharPointer_UTF16 { chars.data() } };
}
std::vector<std::byte> Encodings::stringTo7BitText (const String& text)
{
std::vector<std::byte> result;
for (const auto character : text)
{
if (character == 0x0a || (0x20 <= character && character < 0x80))
{
result.emplace_back (std::byte (character));
}
else
{
// Suspiciously low ASCII value encountered!
jassert (character >= 0x80);
CharPointer_UTF16::CharType points[2]{};
CharPointer_UTF16 asUTF16 { points };
asUTF16.write (character);
std::for_each (points, asUTF16.getAddress(), [&] (CharPointer_UTF16::CharType unit)
{
const auto str = String::toHexString (unit);
result.insert (result.end(), { std::byte { '\\' }, std::byte { 'u' } });
for (const auto c : str)
result.push_back ((std::byte) c);
});
}
}
return result;
}
std::vector<std::byte> Encodings::toMcoded7 (Span<const std::byte> bytes)
{
std::vector<std::byte> result;
result.reserve ((bytes.size() * 8) + 6 / 7);
for (size_t index = 0; index < bytes.size(); index += 7)
{
std::array<std::byte, 7> slice{};
const auto sliceSize = std::min ((size_t) 7, bytes.size() - index);
std::copy (bytes.begin() + index, bytes.begin() + index + sliceSize, slice.begin());
result.push_back ((slice[0] & std::byte { 0x80 }) >> 1
| (slice[1] & std::byte { 0x80 }) >> 2
| (slice[2] & std::byte { 0x80 }) >> 3
| (slice[3] & std::byte { 0x80 }) >> 4
| (slice[4] & std::byte { 0x80 }) >> 5
| (slice[5] & std::byte { 0x80 }) >> 6
| (slice[6] & std::byte { 0x80 }) >> 7);
std::transform (slice.begin(),
std::next (slice.begin(), (ptrdiff_t) sliceSize),
std::back_inserter (result),
[] (const std::byte b) { return b & std::byte { 0x7f }; });
}
return result;
}
std::vector<std::byte> Encodings::fromMcoded7 (Span<const std::byte> bytes)
{
std::vector<std::byte> result;
result.reserve ((bytes.size() * 7) + 7 / 8);
for (size_t index = 0; index < bytes.size(); index += 8)
{
const auto sliceSize = std::min ((size_t) 7, bytes.size() - index - 1);
for (size_t i = 0; i < sliceSize; ++i)
{
const auto highBit = (bytes[index] & std::byte { (uint8_t) (1 << (6 - i)) }) << (i + 1);
result.push_back (highBit | bytes[index + 1 + i]);
}
}
return result;
}
std::optional<std::vector<std::byte>> Encodings::tryEncode (Span<const std::byte> bytes, Encoding mutualEncoding)
{
switch (mutualEncoding)
{
case Encoding::ascii:
{
if (std::all_of (bytes.begin(), bytes.end(), [] (auto b) { return (b & std::byte { 0x80 }) == std::byte{}; }))
return std::vector<std::byte> (bytes.begin(), bytes.end());
jassertfalse;
return {};
}
case Encoding::mcoded7:
return toMcoded7 (bytes);
case Encoding::zlibAndMcoded7:
{
MemoryOutputStream memoryStream;
GZIPCompressorOutputStream (memoryStream).write (bytes.data(), bytes.size());
return toMcoded7 (Span (static_cast<const std::byte*> (memoryStream.getData()), memoryStream.getDataSize()));
}
}
// Unknown encoding!
jassertfalse;
return {};
}
std::vector<std::byte> Encodings::decode (Span<const std::byte> bytes, Encoding mutualEncoding)
{
if (mutualEncoding == Encoding::ascii)
{
// All values must be 7-bit!
jassert (std::none_of (bytes.begin(), bytes.end(), [] (const auto& b) { return (b & std::byte { 0x80 }) != std::byte{}; }));
return std::vector<std::byte> (bytes.begin(), bytes.end());
}
if (mutualEncoding == Encoding::mcoded7)
return fromMcoded7 (bytes);
if (mutualEncoding == Encoding::zlibAndMcoded7)
{
const auto mcoded = fromMcoded7 (bytes);
MemoryInputStream memoryStream (mcoded.data(), mcoded.size(), false);
GZIPDecompressorInputStream zipStream (memoryStream);
const size_t chunkSize = 1 << 8;
std::vector<std::byte> result;
for (;;)
{
const auto previousSize = result.size();
result.resize (previousSize + chunkSize);
const auto read = zipStream.read (result.data() + previousSize, chunkSize);
if (read < 0)
{
// Decompression failed!
jassertfalse;
return {};
}
result.resize (previousSize + (size_t) read);
if (read == 0)
return result;
}
}
// Unknown encoding!
jassertfalse;
return {};
}
#if JUCE_UNIT_TESTS
class EncodingsTests : public UnitTest
{
public:
EncodingsTests() : UnitTest ("Encodings", UnitTestCategories::midi) {}
void runTest() override
{
beginTest ("7-bit text encoding");
{
{
const auto converted = Encodings::stringTo7BitText (juce::CharPointer_UTF8 ("Accepted Beat \xe2\x99\xaa"));
const auto expected = makeByteArray ('A', 'c', 'c', 'e', 'p', 't', 'e', 'd', ' ', 'B', 'e', 'a', 't', ' ', '\\', 'u', '2', '6', '6', 'a');
expect (std::equal (converted.begin(), converted.end(), expected.begin(), expected.end()));
}
{
const auto converted = Encodings::stringTo7BitText (juce::CharPointer_UTF8 ("\xe6\xae\x8b\xe3\x82\x8a\xe3\x82\x8f\xe3\x81\x9a\xe3\x81\x8b""5\xe3\x83\x90\xe3\x82\xa4\xe3\x83\x88"));
const auto expected = makeByteArray ('\\', 'u', '6', 'b', '8', 'b',
'\\', 'u', '3', '0', '8', 'a',
'\\', 'u', '3', '0', '8', 'f',
'\\', 'u', '3', '0', '5', 'a',
'\\', 'u', '3', '0', '4', 'b',
'5',
'\\', 'u', '3', '0', 'd', '0',
'\\', 'u', '3', '0', 'a', '4',
'\\', 'u', '3', '0', 'c', '8');
expect (std::equal (converted.begin(), converted.end(), expected.begin(), expected.end()));
}
}
beginTest ("7-bit text decoding");
{
{
const auto converted = Encodings::stringFrom7BitText (makeByteArray ('A', 'c', 'c', 'e', 'p', 't', 'e', 'd', ' ', 'B', 'e', 'a', 't', ' ', '\\', 'u', '2', '6', '6', 'a'));
const String expected = juce::CharPointer_UTF8 ("Accepted Beat \xe2\x99\xaa");
expect (converted == expected);
}
{
const auto converted = Encodings::stringFrom7BitText (makeByteArray ('\\', 'u', '6', 'b', '8', 'b',
'\\', 'u', '3', '0', '8', 'a',
'\\', 'u', '3', '0', '8', 'f',
'\\', 'u', '3', '0', '5', 'a',
'\\', 'u', '3', '0', '4', 'b',
'5',
'\\', 'u', '3', '0', 'd', '0',
'\\', 'u', '3', '0', 'a', '4',
'\\', 'u', '3', '0', 'c', '8'));
const String expected = juce::CharPointer_UTF8 ("\xe6\xae\x8b\xe3\x82\x8a\xe3\x82\x8f\xe3\x81\x9a\xe3\x81\x8b""5\xe3\x83\x90\xe3\x82\xa4\xe3\x83\x88");
expect (converted == expected);
}
}
beginTest ("Mcoded7 encoding");
{
{
const auto converted = Encodings::toMcoded7 (makeByteArray (0x81, 0x82, 0x83));
const auto expected = makeByteArray (0x70, 0x01, 0x02, 0x03);
expect (rangesEqual (converted, expected));
}
{
const auto converted = Encodings::toMcoded7 (makeByteArray (0x01, 0x82, 0x03, 0x04, 0x85, 0x06, 0x87, 0x08));
const auto expected = makeByteArray (0x25, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x08);
expect (rangesEqual (converted, expected));
}
}
beginTest ("Mcoded7 decoding");
{
{
const auto converted = Encodings::fromMcoded7 (makeByteArray (0x70, 0x01, 0x02, 0x03));
const auto expected = makeByteArray (0x81, 0x82, 0x83);
expect (rangesEqual (converted, expected));
}
{
const auto converted = Encodings::fromMcoded7 (makeByteArray (0x25, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x08));
const auto expected = makeByteArray (0x01, 0x82, 0x03, 0x04, 0x85, 0x06, 0x87, 0x08);
expect (rangesEqual (converted, expected));
}
}
}
private:
static bool deepEqual (const std::optional<var>& a, const std::optional<var>& b)
{
if (a.has_value() && b.has_value())
return JSONUtils::deepEqual (*a, *b);
return a == b;
}
template <typename A, typename B>
static bool rangesEqual (A&& a, B&& b)
{
using std::begin, std::end;
return std::equal (begin (a), end (a), begin (b), end (b));
}
template <typename... Ts>
static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
{
jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
return { std::byte (ts)... };
}
};
static EncodingsTests encodingsTests;
#endif
} // namespace juce::midi_ci

View file

@ -0,0 +1,87 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Utility functions for working with data formats used by property exchange
messages.
@tags{Audio}
*/
struct Encodings
{
/** Text in ACK and NAK messages can't be utf-8 or ASCII because each byte only has 7 usable bits.
The encoding rules are in section 5.10.4 of the CI spec.
*/
static String stringFrom7BitText (Span<const std::byte> bytes);
/** Text in ACK and NAK messages can't be utf-8 or ASCII because each byte only has 7 usable bits.
The encoding rules are in section 5.10.4 of the CI spec.
*/
static std::vector<std::byte> stringTo7BitText (const String& text);
/** Converts a list of bytes representing a 7-bit ASCII string to JSON. */
static var jsonFrom7BitText (Span<const std::byte> bytes)
{
return JSON::parse (stringFrom7BitText (bytes));
}
/** Converts a JSON object to a list of bytes in 7-bit ASCII format. */
static std::vector<std::byte> jsonTo7BitText (const var& v)
{
return stringTo7BitText (JSON::toString (v, true));
}
/** Each group of seven stored bytes is transmitted as eight bytes.
First, the sign bits of the seven bytes are sent, followed by the low-order 7 bits of each byte.
*/
static std::vector<std::byte> toMcoded7 (Span<const std::byte> bytes);
/** Each group of seven stored bytes is transmitted as eight bytes.
First, the sign bits of the seven bytes are sent, followed by the low-order 7 bits of each byte.
*/
static std::vector<std::byte> fromMcoded7 (Span<const std::byte> bytes);
/** Attempts to encode the provided byte span using the specified encoding.
The ASCII encoding does not make any changes to the input stream, but
encoding will fail if any byte has its most significant bit set.
*/
static std::optional<std::vector<std::byte>> tryEncode (Span<const std::byte> bytes,
Encoding mutualEncoding);
/** Decodes the provided byte span using the specified encoding.
All bytes of the input must be 7-bit values, i.e. all most-significant bits
are unset.
*/
static std::vector<std::byte> decode (Span<const std::byte> bytes, Encoding mutualEncoding);
Encodings() = delete;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,49 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Contains information about a MIDI 2.0 function block.
@tags{Audio}
*/
struct FunctionBlock
{
std::byte identifier { 0x7f }; ///< 0x7f == no function block
uint8_t firstGroup = 0; ///< The first group that is part of the block, 0-based
uint8_t numGroups = 1; ///< The number of groups contained in the block
bool operator== (const FunctionBlock& other) const
{
const auto tie = [] (auto& x) { return std::tie (x.identifier, x.firstGroup, x.numGroups); };
return tie (*this) == tie (other);
}
bool operator!= (const FunctionBlock& other) const { return ! operator== (other); }
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,664 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
//==============================================================================
/**
Byte values representing different addresses within a group.
@tags{Audio}
*/
enum class ChannelInGroup : uint8_t
{
channel0 = 0x0,
channel1 = 0x1,
channel2 = 0x2,
channel3 = 0x3,
channel4 = 0x4,
channel5 = 0x5,
channel6 = 0x6,
channel7 = 0x7,
channel8 = 0x8,
channel9 = 0x9,
channelA = 0xA,
channelB = 0xB,
channelC = 0xC,
channelD = 0xD,
channelE = 0xE,
channelF = 0xF,
wholeGroup = 0x7e, ///< Refers to all channels in the UMP group
wholeBlock = 0x7f, ///< Refers to all channels in the function block that contains the UMP group
};
struct ChannelInGroupUtils
{
ChannelInGroupUtils() = delete;
/** Converts a ChannelInGroup to a descriptive string. */
static String toString (ChannelInGroup c)
{
if (c == ChannelInGroup::wholeGroup)
return "Group";
if (c == ChannelInGroup::wholeBlock)
return "Function Block";
const auto underlying = (std::underlying_type_t<ChannelInGroup>) c;
return "Channel " + String (underlying + 1);
}
};
using Profile = std::array<std::byte, 5>;
//==============================================================================
/**
Namespace containing structs representing different kinds of MIDI-CI message.
@tags{Audio}
*/
namespace Message
{
/** Wraps a span, providing equality operators that compare the span
contents elementwise.
*/
template <typename T>
struct ComparableRange
{
T& data;
bool operator== (const ComparableRange& other) const
{
return std::equal (data.begin(), data.end(), other.data.begin(), other.data.end());
}
bool operator!= (const ComparableRange& other) const { return ! operator== (other); }
};
template <typename T> static constexpr auto makeComparableRange ( T& t) { return ComparableRange< T> { t }; }
template <typename T> static constexpr auto makeComparableRange (const T& t) { return ComparableRange<const T> { t }; }
//==============================================================================
/**
Holds fields that can be found at the beginning of every MIDI CI message.
*/
struct Header
{
ChannelInGroup deviceID{};
std::byte category{};
std::byte version{};
MUID source = MUID::makeUnchecked (0);
MUID destination = MUID::makeUnchecked (0);
auto tie() const
{
return std::tuple (deviceID, category, version, source, destination);
}
bool operator== (const Header& x) const { return tie() == x.tie(); }
bool operator!= (const Header& x) const { return ! operator== (x); }
};
/**
Groups together a CI message header, and some number of trailing bytes.
*/
struct Generic
{
Header header;
Span<const std::byte> data;
};
//==============================================================================
/** See the MIDI-CI specification. */
struct DiscoveryResponse
{
ump::DeviceInfo device;
std::byte capabilities{};
uint32_t maximumSysexSize{};
std::byte outputPathID{}; /**< Only valid if the message header specifies version 0x02 or greater. */
std::byte functionBlock{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (device, capabilities, maximumSysexSize, outputPathID, functionBlock);
}
bool operator== (const DiscoveryResponse& x) const { return tie() == x.tie(); }
bool operator!= (const DiscoveryResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct Discovery
{
ump::DeviceInfo device;
std::byte capabilities{};
uint32_t maximumSysexSize{};
std::byte outputPathID{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (device, capabilities, maximumSysexSize, outputPathID);
}
bool operator== (const Discovery& x) const { return tie() == x.tie(); }
bool operator!= (const Discovery& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct EndpointInquiryResponse
{
std::byte status;
Span<const std::byte> data;
auto tie() const
{
return std::tuple (status, makeComparableRange (data));
}
bool operator== (const EndpointInquiryResponse& x) const { return tie() == x.tie(); }
bool operator!= (const EndpointInquiryResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct EndpointInquiry
{
std::byte status;
auto tie() const
{
return std::tuple (status);
}
bool operator== (const EndpointInquiry& x) const { return tie() == x.tie(); }
bool operator!= (const EndpointInquiry& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct InvalidateMUID
{
MUID target = MUID::makeUnchecked (0);
auto tie() const
{
return std::tuple (target);
}
bool operator== (const InvalidateMUID& x) const { return tie() == x.tie(); }
bool operator!= (const InvalidateMUID& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ACK
{
std::byte originalCategory{};
std::byte statusCode{};
std::byte statusData{};
std::array<std::byte, 5> details{};
Span<const std::byte> messageText{};
/** Convenience function that returns the message's text as a String. */
String getMessageTextAsString() const
{
return Encodings::stringFrom7BitText (messageText);
}
auto tie() const
{
return std::tuple (originalCategory, statusCode, statusData, details, makeComparableRange (messageText));
}
bool operator== (const ACK& x) const { return tie() == x.tie(); }
bool operator!= (const ACK& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct NAK
{
std::byte originalCategory{}; /**< Only valid if the message header specifies version 0x02 or greater. */
std::byte statusCode{}; /**< Only valid if the message header specifies version 0x02 or greater. */
std::byte statusData{}; /**< Only valid if the message header specifies version 0x02 or greater. */
std::array<std::byte, 5> details{}; /**< Only valid if the message header specifies version 0x02 or greater. */
Span<const std::byte> messageText{}; /**< Only valid if the message header specifies version 0x02 or greater. */
/** Convenience function that returns the message's text as a String. */
String getMessageTextAsString() const
{
return Encodings::stringFrom7BitText (messageText);
}
auto tie() const
{
return std::tuple (originalCategory, statusCode, statusData, details, makeComparableRange (messageText));
}
bool operator== (const NAK& x) const { return tie() == x.tie(); }
bool operator!= (const NAK& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileInquiryResponse
{
Span<const Profile> enabledProfiles;
Span<const Profile> disabledProfiles;
auto tie() const
{
return std::tuple (makeComparableRange (enabledProfiles), makeComparableRange (disabledProfiles));
}
bool operator== (const ProfileInquiryResponse& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileInquiryResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileInquiry
{
auto tie() const
{
return std::tuple();
}
bool operator== (const ProfileInquiry& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileInquiry& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileAdded
{
Profile profile{};
auto tie() const
{
return std::tuple (profile);
}
bool operator== (const ProfileAdded& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileAdded& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileRemoved
{
Profile profile{};
auto tie() const
{
return std::tuple (profile);
}
bool operator== (const ProfileRemoved& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileRemoved& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileDetailsResponse
{
Profile profile{};
std::byte target{};
Span<const std::byte> data;
auto tie() const
{
return std::tuple (profile, target, makeComparableRange (data));
}
bool operator== (const ProfileDetailsResponse& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileDetailsResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileDetails
{
Profile profile{};
std::byte target{};
auto tie() const
{
return std::tuple (profile, target);
}
bool operator== (const ProfileDetails& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileDetails& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileOn
{
Profile profile{};
uint16_t numChannels{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (profile, numChannels);
}
bool operator== (const ProfileOn& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileOn& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileOff
{
Profile profile{};
auto tie() const
{
return std::tuple (profile);
}
bool operator== (const ProfileOff& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileOff& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileEnabledReport
{
Profile profile{};
uint16_t numChannels{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (profile, numChannels);
}
bool operator== (const ProfileEnabledReport& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileEnabledReport& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileDisabledReport
{
Profile profile{};
uint16_t numChannels{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (profile, numChannels);
}
bool operator== (const ProfileDisabledReport& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileDisabledReport& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProfileSpecificData
{
Profile profile{};
Span<const std::byte> data;
auto tie() const
{
return std::tuple (profile, makeComparableRange (data));
}
bool operator== (const ProfileSpecificData& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileSpecificData& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertyExchangeCapabilitiesResponse
{
std::byte numSimultaneousRequestsSupported{};
std::byte majorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */
std::byte minorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (numSimultaneousRequestsSupported, majorVersion, minorVersion);
}
bool operator== (const PropertyExchangeCapabilitiesResponse& x) const { return tie() == x.tie(); }
bool operator!= (const PropertyExchangeCapabilitiesResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertyExchangeCapabilities
{
std::byte numSimultaneousRequestsSupported{};
std::byte majorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */
std::byte minorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */
auto tie() const
{
return std::tuple (numSimultaneousRequestsSupported, majorVersion, minorVersion);
}
bool operator== (const PropertyExchangeCapabilities& x) const { return tie() == x.tie(); }
bool operator!= (const PropertyExchangeCapabilities& x) const { return ! operator== (x); }
};
/** A property-exchange message that has no payload, and must therefore
be contained in a single chunk.
*/
struct StaticSizePropertyExchange
{
std::byte requestID{};
Span<const std::byte> header;
auto tie() const
{
return std::tuple (requestID, makeComparableRange (header));
}
};
/** A property-exchange message that may form part of a multi-chunk
message sequence.
*/
struct DynamicSizePropertyExchange
{
std::byte requestID{};
Span<const std::byte> header;
uint16_t totalNumChunks{};
uint16_t thisChunkNum{};
Span<const std::byte> data;
auto tie() const
{
return std::tuple (requestID,
makeComparableRange (header),
totalNumChunks,
thisChunkNum,
makeComparableRange (data));
}
};
/** See the MIDI-CI specification. */
struct PropertyGetDataResponse : public DynamicSizePropertyExchange
{
bool operator== (const PropertyGetDataResponse& x) const { return tie() == x.tie(); }
bool operator!= (const PropertyGetDataResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertyGetData : public StaticSizePropertyExchange
{
bool operator== (const PropertyGetData& x) const { return tie() == x.tie(); }
bool operator!= (const PropertyGetData& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertySetDataResponse : public StaticSizePropertyExchange
{
bool operator== (const PropertySetDataResponse& x) const { return tie() == x.tie(); }
bool operator!= (const PropertySetDataResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertySetData : public DynamicSizePropertyExchange
{
bool operator== (const PropertySetData& x) const { return tie() == x.tie(); }
bool operator!= (const PropertySetData& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertySubscribeResponse : public DynamicSizePropertyExchange
{
bool operator== (const PropertySubscribeResponse& x) const { return tie() == x.tie(); }
bool operator!= (const PropertySubscribeResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertySubscribe : public DynamicSizePropertyExchange
{
bool operator== (const PropertySubscribe& x) const { return tie() == x.tie(); }
bool operator!= (const PropertySubscribe& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct PropertyNotify : public DynamicSizePropertyExchange
{
bool operator== (const PropertyNotify& x) const { return tie() == x.tie(); }
bool operator!= (const PropertyNotify& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProcessInquiryResponse
{
std::byte supportedFeatures{};
auto tie() const
{
return std::tuple (supportedFeatures);
}
bool operator== (const ProcessInquiryResponse& x) const { return tie() == x.tie(); }
bool operator!= (const ProcessInquiryResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProcessInquiry
{
auto tie() const
{
return std::tuple();
}
bool operator== (const ProcessInquiry& x) const { return tie() == x.tie(); }
bool operator!= (const ProcessInquiry& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProcessMidiMessageReportResponse
{
std::byte messageDataControl{};
std::byte requestedMessages{};
std::byte channelControllerMessages{};
std::byte noteDataMessages{};
auto tie() const
{
return std::tuple (messageDataControl, requestedMessages, channelControllerMessages, noteDataMessages);
}
bool operator== (const ProcessMidiMessageReportResponse& x) const { return tie() == x.tie(); }
bool operator!= (const ProcessMidiMessageReportResponse& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProcessMidiMessageReport
{
std::byte messageDataControl{};
std::byte requestedMessages{};
std::byte channelControllerMessages{};
std::byte noteDataMessages{};
auto tie() const
{
return std::tuple (messageDataControl, requestedMessages, channelControllerMessages, noteDataMessages);
}
bool operator== (const ProcessMidiMessageReport& x) const { return tie() == x.tie(); }
bool operator!= (const ProcessMidiMessageReport& x) const { return ! operator== (x); }
};
/** See the MIDI-CI specification. */
struct ProcessEndMidiMessageReport
{
auto tie() const
{
return std::tuple();
}
bool operator== (const ProcessEndMidiMessageReport& x) const { return tie() == x.tie(); }
bool operator!= (const ProcessEndMidiMessageReport& x) const { return ! operator== (x); }
};
/**
A message with a header and optional body.
The body may be set to std::monostate to indicate some kind of failure, such as a malformed
incoming message.
*/
struct Parsed
{
using Body = std::variant<std::monostate,
Discovery,
DiscoveryResponse,
InvalidateMUID,
EndpointInquiry,
EndpointInquiryResponse,
ACK,
NAK,
ProfileInquiry,
ProfileInquiryResponse,
ProfileAdded,
ProfileRemoved,
ProfileDetails,
ProfileDetailsResponse,
ProfileOn,
ProfileOff,
ProfileEnabledReport,
ProfileDisabledReport,
ProfileSpecificData,
PropertyExchangeCapabilities,
PropertyExchangeCapabilitiesResponse,
PropertyGetData,
PropertyGetDataResponse,
PropertySetData,
PropertySetDataResponse,
PropertySubscribe,
PropertySubscribeResponse,
PropertyNotify,
ProcessInquiry,
ProcessInquiryResponse,
ProcessMidiMessageReport,
ProcessMidiMessageReportResponse,
ProcessEndMidiMessageReport>;
Header header;
Body body;
bool operator== (const Parsed& other) const
{
const auto tie = [] (const auto& x) { return std::tie (x.header, x.body); };
return tie (*this) == tie (other);
}
bool operator!= (const Parsed& other) const { return ! operator== (other); }
};
}
} // namespace juce::midi_ci

View file

@ -0,0 +1,83 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
A 28-bit ID that uniquely identifies a device taking part in a series of
MIDI-CI transactions.
@tags{Audio}
*/
class MUID
{
constexpr explicit MUID (uint32_t v) : value (v) {}
// 0x0fffff00 to 0x0ffffffe are reserved, 0x0fffffff is 'broadcast'
static constexpr uint32_t userMuidEnd = 0x0fffff00;
static constexpr uint32_t mask = 0x0fffffff;
uint32_t value{};
public:
/** Returns the ID as a plain integer. */
constexpr uint32_t get() const { return value; }
/** Converts the provided integer to a MUID without validation that it
is within the allowed range.
*/
static MUID makeUnchecked (uint32_t v)
{
// If this is hit, the MUID has too many bits set!
jassert ((v & mask) == v);
return MUID (v);
}
/** Returns a MUID if the provided value is within the valid range for
MUID values; otherwise returns nullopt.
*/
static std::optional<MUID> make (uint32_t v)
{
if ((v & mask) == v)
return makeUnchecked (v);
return {};
}
/** Makes a random MUID using the provided random engine. */
static MUID makeRandom (Random& r)
{
return makeUnchecked ((uint32_t) r.nextInt (userMuidEnd));
}
bool operator== (const MUID other) const { return value == other.value; }
bool operator!= (const MUID other) const { return value != other.value; }
bool operator< (const MUID other) const { return value < other.value; }
/** Returns the special MUID representing the broadcast address. */
static constexpr MUID getBroadcast() { return MUID { mask }; }
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,448 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
std::optional<Message::Parsed> Parser::parse (Span<const std::byte> message, Status* status)
{
const auto setStatus = [&] (Status s)
{
if (status != nullptr)
*status = s;
};
setStatus (Status::noError);
Message::Generic generic;
if (! detail::Marshalling::Reader { message } (generic))
{
// Got a full sysex message, but it didn't contain a well-formed header.
setStatus (Status::malformed);
return {};
}
if ((generic.header.version & std::byte { 0x70 }) != std::byte{})
{
setStatus (Status::reservedVersion);
return Message::Parsed { generic.header, std::monostate{} };
}
const auto index = (uint8_t) generic.header.category;
constexpr auto tables = detail::MessageTypeUtils::getTables();
const auto processFunction = tables.parsers[index];
return Message::Parsed { generic.header, processFunction (generic, status) };
}
std::optional<Message::Parsed> Parser::parse (const MUID ourMUID,
Span<const std::byte> message,
Status* status)
{
const auto setStatus = [&] (Status s)
{
if (status != nullptr)
*status = s;
};
setStatus (Status::noError);
if (const auto parsed = parse (message, status))
{
if (parsed->header.destination != MUID::getBroadcast() && parsed->header.destination != ourMUID)
setStatus (Status::mismatchedMUID);
else if (parsed->header.source == ourMUID)
setStatus (Status::collidingMUID);
else if ((parsed->header.version & std::byte { 0x70 }) != std::byte{})
setStatus (Status::reservedVersion);
return parsed;
}
return {};
}
class DescriptionVisitor : public detail::MessageTypeUtils::MessageVisitor
{
public:
DescriptionVisitor (const Message::Parsed* m, String* str) : msg (m), result (str) {}
void visit (const std::monostate&) const override { *result = "!! Unrecognised !!"; }
void visit (const Message::Discovery& body) const override { visitImpl (body); }
void visit (const Message::DiscoveryResponse& body) const override { visitImpl (body); }
void visit (const Message::InvalidateMUID& body) const override { visitImpl (body); }
void visit (const Message::EndpointInquiry& body) const override { visitImpl (body); }
void visit (const Message::EndpointInquiryResponse& body) const override { visitImpl (body); }
void visit (const Message::ACK& body) const override { visitImpl (body); }
void visit (const Message::NAK& body) const override { visitImpl (body); }
void visit (const Message::ProfileInquiry& body) const override { visitImpl (body); }
void visit (const Message::ProfileInquiryResponse& body) const override { visitImpl (body); }
void visit (const Message::ProfileAdded& body) const override { visitImpl (body); }
void visit (const Message::ProfileRemoved& body) const override { visitImpl (body); }
void visit (const Message::ProfileDetails& body) const override { visitImpl (body); }
void visit (const Message::ProfileDetailsResponse& body) const override { visitImpl (body); }
void visit (const Message::ProfileOn& body) const override { visitImpl (body); }
void visit (const Message::ProfileOff& body) const override { visitImpl (body); }
void visit (const Message::ProfileEnabledReport& body) const override { visitImpl (body); }
void visit (const Message::ProfileDisabledReport& body) const override { visitImpl (body); }
void visit (const Message::ProfileSpecificData& body) const override { visitImpl (body); }
void visit (const Message::PropertyExchangeCapabilities& body) const override { visitImpl (body); }
void visit (const Message::PropertyExchangeCapabilitiesResponse& body) const override { visitImpl (body); }
void visit (const Message::PropertyGetData& body) const override { visitImpl (body); }
void visit (const Message::PropertyGetDataResponse& body) const override { visitImpl (body); }
void visit (const Message::PropertySetData& body) const override { visitImpl (body); }
void visit (const Message::PropertySetDataResponse& body) const override { visitImpl (body); }
void visit (const Message::PropertySubscribe& body) const override { visitImpl (body); }
void visit (const Message::PropertySubscribeResponse& body) const override { visitImpl (body); }
void visit (const Message::PropertyNotify& body) const override { visitImpl (body); }
void visit (const Message::ProcessInquiry& body) const override { visitImpl (body); }
void visit (const Message::ProcessInquiryResponse& body) const override { visitImpl (body); }
void visit (const Message::ProcessMidiMessageReport& body) const override { visitImpl (body); }
void visit (const Message::ProcessMidiMessageReportResponse& body) const override { visitImpl (body); }
void visit (const Message::ProcessEndMidiMessageReport& body) const override { visitImpl (body); }
private:
static const char* getDescription (const Message::Discovery&) { return "Discovery"; }
static const char* getDescription (const Message::DiscoveryResponse&) { return "Discovery Response"; }
static const char* getDescription (const Message::InvalidateMUID&) { return "Invalidate MUID"; }
static const char* getDescription (const Message::EndpointInquiry&) { return "Endpoint"; }
static const char* getDescription (const Message::EndpointInquiryResponse&) { return "Endpoint Response"; }
static const char* getDescription (const Message::ACK&) { return "ACK"; }
static const char* getDescription (const Message::NAK&) { return "NAK"; }
static const char* getDescription (const Message::ProfileInquiry&) { return "Profile Inquiry"; }
static const char* getDescription (const Message::ProfileInquiryResponse&) { return "Profile Inquiry Response"; }
static const char* getDescription (const Message::ProfileAdded&) { return "Profile Added"; }
static const char* getDescription (const Message::ProfileRemoved&) { return "Profile Removed"; }
static const char* getDescription (const Message::ProfileDetails&) { return "Profile Details"; }
static const char* getDescription (const Message::ProfileDetailsResponse&) { return "Profile Details Response"; }
static const char* getDescription (const Message::ProfileOn&) { return "Profile On"; }
static const char* getDescription (const Message::ProfileOff&) { return "Profile Off"; }
static const char* getDescription (const Message::ProfileEnabledReport&) { return "Profile Enabled Report"; }
static const char* getDescription (const Message::ProfileDisabledReport&) { return "Profile Disabled Report"; }
static const char* getDescription (const Message::ProfileSpecificData&) { return "Profile Specific Data"; }
static const char* getDescription (const Message::PropertyExchangeCapabilities&) { return "Property Exchange Capabilities"; }
static const char* getDescription (const Message::PropertyExchangeCapabilitiesResponse&) { return "Property Exchange Capabilities Response"; }
static const char* getDescription (const Message::PropertyGetData&) { return "Property Get Data"; }
static const char* getDescription (const Message::PropertyGetDataResponse&) { return "Property Get Data Response"; }
static const char* getDescription (const Message::PropertySetData&) { return "Property Set Data"; }
static const char* getDescription (const Message::PropertySetDataResponse&) { return "Property Set Data Response"; }
static const char* getDescription (const Message::PropertySubscribe&) { return "Property Subscribe"; }
static const char* getDescription (const Message::PropertySubscribeResponse&) { return "Property Subscribe Response"; }
static const char* getDescription (const Message::PropertyNotify&) { return "Property Notify"; }
static const char* getDescription (const Message::ProcessInquiry&) { return "Process Inquiry"; }
static const char* getDescription (const Message::ProcessInquiryResponse&) { return "Process Inquiry Response"; }
static const char* getDescription (const Message::ProcessMidiMessageReport&) { return "Process Midi Message Report"; }
static const char* getDescription (const Message::ProcessMidiMessageReportResponse&) { return "Process Midi Message Report Response"; }
static const char* getDescription (const Message::ProcessEndMidiMessageReport&) { return "Process End Midi Message Report"; }
template <typename Body>
void visitImpl (const Body& body) const
{
const auto opts = ToVarOptions{}.withExplicitVersion ((int) msg->header.version)
.withVersionIncluded (false);
const auto json = ToVar::convert (body, opts);
if (json.has_value())
*result = String (getDescription (body)) + ": " + JSON::toString (*json, true);
}
const Message::Parsed* msg = nullptr;
String* result = nullptr;
};
String Parser::getMessageDescription (const Message::Parsed& message)
{
String result;
detail::MessageTypeUtils::visit (message, DescriptionVisitor { &message, &result });
return result;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class ParserTests : public UnitTest
{
public:
ParserTests() : UnitTest ("Parser", UnitTestCategories::midi) {}
void runTest() override
{
auto random = getRandom();
beginTest ("Sending an empty message does nothing");
{
const auto parsed = Parser::parse (MUID::makeRandom (random), {});
expect (parsed == std::nullopt);
}
beginTest ("Sending a garbage message does nothing");
{
const std::vector<std::byte> bytes (128, std::byte { 0x70 });
const auto parsed = Parser::parse (MUID::makeRandom (random), bytes);
expect (parsed == std::nullopt);
}
beginTest ("Sending a message with truncated body produces a malformed status");
{
constexpr auto version1 = 0x01;
const auto truncatedV1 = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* discovery message */ 0x70,
/* version */ version1,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* broadcast MUID */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* manufacturer */ 0x10,
/* ... */ 0x11,
/* ... */ 0x12,
/* family */ 0x20,
/* ... */ 0x21,
/* model */ 0x30,
/* ... */ 0x31,
/* revision */ 0x40,
/* ... */ 0x41,
/* ... */ 0x42,
/* ... */ 0x43,
/* CI category supported */ 0x7f,
/* max sysex size */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f);
/* Missing final byte for a version 1 message */
Parser::Status status{};
const auto parsedV1 = Parser::parse (MUID::makeRandom (random), truncatedV1, &status);
expect (status == Parser::Status::malformed);
expect (parsedV1 == Message::Parsed { Message::Header { ChannelInGroup::wholeBlock,
std::byte { 0x70 },
std::byte { version1 },
MUID::makeUnchecked (0x80c101),
MUID::getBroadcast() },
std::monostate{} });
constexpr auto version2 = 0x02;
const auto truncatedV2 = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* discovery message */ 0x70,
/* version */ version2,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* broadcast MUID */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* manufacturer */ 0x10,
/* ... */ 0x11,
/* ... */ 0x12,
/* family */ 0x20,
/* ... */ 0x21,
/* model */ 0x30,
/* ... */ 0x31,
/* revision */ 0x40,
/* ... */ 0x41,
/* ... */ 0x42,
/* ... */ 0x43,
/* CI category supported */ 0x7f,
/* max sysex size */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f);
/* Missing final byte for a version 2 message */
const auto parsedV2 = Parser::parse (MUID::makeRandom (random), truncatedV2);
expect (status == Parser::Status::malformed);
expect (parsedV2 == Message::Parsed { Message::Header { ChannelInGroup::wholeBlock,
std::byte { 0x70 },
std::byte { version2 },
MUID::makeUnchecked (0x80c101),
MUID::getBroadcast() },
std::monostate{} });
}
const auto getExpectedDiscoveryInput = [] (uint8_t version, uint8_t outputPathID)
{
return Message::Parsed { Message::Header { ChannelInGroup::wholeBlock,
std::byte { 0x70 },
std::byte { version },
MUID::makeUnchecked (0x80c101),
MUID::getBroadcast() },
Message::Discovery { { { std::byte { 0x10 }, std::byte { 0x11 }, std::byte { 0x12 } },
{ std::byte { 0x20 }, std::byte { 0x21 } },
{ std::byte { 0x30 }, std::byte { 0x31 } },
{ std::byte { 0x40 }, std::byte { 0x41 }, std::byte { 0x42 }, std::byte { 0x43 } } },
std::byte { 0x7f },
0xfffffff,
std::byte { outputPathID } } };
};
beginTest ("Sending a V1 discovery message notifies the input listener");
{
const auto initialMUID = MUID::makeRandom (random);
constexpr uint8_t version = 0x01;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* discovery message */ 0x70,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* broadcast MUID */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* manufacturer */ 0x10,
/* ... */ 0x11,
/* ... */ 0x12,
/* family */ 0x20,
/* ... */ 0x21,
/* model */ 0x30,
/* ... */ 0x31,
/* revision */ 0x40,
/* ... */ 0x41,
/* ... */ 0x42,
/* ... */ 0x43,
/* CI category supported */ 0x7f,
/* max sysex size */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f);
const auto parsed = Parser::parse (initialMUID, bytes);
expect (parsed == getExpectedDiscoveryInput (version, 0));
}
beginTest ("Sending a V2 discovery message notifies the input listener");
{
constexpr uint8_t outputPathID = 5;
const auto initialMUID = MUID::makeRandom (random);
constexpr uint8_t version = 0x02;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* discovery message */ 0x70,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* broadcast MUID */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* manufacturer */ 0x10,
/* ... */ 0x11,
/* ... */ 0x12,
/* family */ 0x20,
/* ... */ 0x21,
/* model */ 0x30,
/* ... */ 0x31,
/* revision */ 0x40,
/* ... */ 0x41,
/* ... */ 0x42,
/* ... */ 0x43,
/* CI category supported */ 0x7f,
/* max sysex size */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* output path ID */ outputPathID);
const auto parsed = Parser::parse (initialMUID, bytes);
expect (parsed == getExpectedDiscoveryInput (version, outputPathID));
}
beginTest ("Sending a discovery message with a future version notifies the input listener and ignores trailing fields");
{
constexpr uint8_t outputPathID = 10;
const auto initialMUID = MUID::makeRandom (random);
constexpr auto version = (uint8_t) detail::MessageMeta::implementationVersion + 1;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* discovery message */ 0x70,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* broadcast MUID */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* manufacturer */ 0x10,
/* ... */ 0x11,
/* ... */ 0x12,
/* family */ 0x20,
/* ... */ 0x21,
/* model */ 0x30,
/* ... */ 0x31,
/* revision */ 0x40,
/* ... */ 0x41,
/* ... */ 0x42,
/* ... */ 0x43,
/* CI category supported */ 0x7f,
/* max sysex size */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* ... */ 0x7f,
/* output path ID */ outputPathID,
/* extra bytes */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00);
const auto parsed = Parser::parse (initialMUID, bytes);
expect (parsed == getExpectedDiscoveryInput (version, outputPathID));
}
}
private:
template <typename... Ts>
static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
{
jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
return { std::byte (ts)... };
}
};
static ParserTests parserTests;
#endif
} // namespace juce::midi_ci

View file

@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Parses CI messages.
@tags{Audio}
*/
class Parser
{
public:
Parser() = delete;
enum class Status
{
noError, ///< Parsing was successful
mismatchedMUID, ///< The message destination MUID doesn't match the provided MUID
collidingMUID, ///< The message source MUID matches the provided MUID
unrecognisedMessage, ///< The message ID doesn't correspond to a known message
reservedVersion, ///< The MIDI CI version uses an unrecognised major version
malformed, ///< The message (whole message, or just body) could not be parsed
};
/** Parses the provided message;
Call this with a full CI message. Don't include any "extra" bytes such as
the leading/trailing 0xf0/0xf7 for messages that were originally in bytestream midi format,
or the packet-header bytes from UMP-formatted sysex messages.
Returns nullopt if the message doesn't need to be acknowledged by the entity with the provided MUID,
or if the message is malformed.
Otherwise, returns a parsed header, and optionally a body.
If the body is std::monostate, then something went wrong while parsing. For example, the body
may be malformed, or the CI version might be unrecognised.
*/
static std::optional<Message::Parsed> parse (MUID ourMUID, Span<const std::byte> message, Status* = nullptr);
/** Parses the provided message;
Call this with a full CI message. Don't include any "extra" bytes such as
the leading/trailing 0xf0/0xf7 for messages that were originally in bytestream midi format,
or the packet-header bytes from UMP-formatted sysex messages.
Returns nullopt if the message is malformed.
Otherwise, returns a parsed header, and optionally a body.
If the body is std::monostate, then something went wrong while parsing. For example, the body
may be malformed, or the CI version might be unrecognised.
*/
static std::optional<Message::Parsed> parse (Span<const std::byte> message, Status* = nullptr);
/** Returns a human-readable string describing the message. */
static String getMessageDescription (const Message::Parsed& message);
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,51 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Holds a profile ID, and the address of a group/channel.
@tags{Audio}
*/
class ProfileAtAddress
{
auto tie() const { return std::tie (profile, address); }
public:
Profile profile; ///< The id of a MIDI-CI profile
ChannelAddress address; ///< A group and channel
bool operator== (const ProfileAtAddress& x) const { return tie() == x.tie(); }
bool operator!= (const ProfileAtAddress& x) const { return tie() != x.tie(); }
bool operator< (const ProfileAtAddress& x) const { return tie() < x.tie(); }
bool operator<= (const ProfileAtAddress& x) const { return tie() <= x.tie(); }
bool operator> (const ProfileAtAddress& x) const { return tie() > x.tie(); }
bool operator>= (const ProfileAtAddress& x) const { return tie() >= x.tie(); }
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,57 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
An interface with methods that can be overridden to customise how a Device
implementing profiles responds to profile inquiries.
@tags{Audio}
*/
struct ProfileDelegate
{
ProfileDelegate() = default;
virtual ~ProfileDelegate() = default;
ProfileDelegate (const ProfileDelegate&) = default;
ProfileDelegate (ProfileDelegate&&) = default;
ProfileDelegate& operator= (const ProfileDelegate&) = default;
ProfileDelegate& operator= (ProfileDelegate&&) = default;
/** Called when a remote device requests that a profile is enabled or disabled.
Old MIDI-CI implementations on remote devices may request that a profile
is enabled with zero channels active - in this situation, it is
recommended that you use ProfileHost::enableProfile to enable the
default number of channels for that profile.
*/
virtual void profileEnablementRequested ([[maybe_unused]] MUID x,
[[maybe_unused]] ProfileAtAddress profileAtAddress,
[[maybe_unused]] int numChannels,
[[maybe_unused]] bool enabled) = 0;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,336 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
class ProfileHost::Visitor : public detail::MessageTypeUtils::MessageVisitor
{
public:
Visitor (ProfileHost* h, ResponderOutput* o, bool* b)
: host (h), output (o), handled (b) {}
void visit (const Message::ProfileInquiry& body) const override { visitImpl (body); }
void visit (const Message::ProfileDetails& body) const override { visitImpl (body); }
void visit (const Message::ProfileOn& body) const override { visitImpl (body); }
void visit (const Message::ProfileOff& body) const override { visitImpl (body); }
using MessageVisitor::visit;
static auto getNumChannels (Message::Header header, Message::ProfileOn p)
{
return (uint8_t) header.version >= 2 ? p.numChannels : 1;
}
static auto getNumChannels (Message::Header, Message::ProfileOff) { return 0; }
private:
template <typename Body>
void visitImpl (const Body& body) const { *handled = messageReceived (body); }
bool messageReceived (const Message::ProfileInquiry&) const
{
host->isResponder = true;
if ((uint8_t) output->getIncomingHeader().deviceID < 16
|| output->getIncomingHeader().deviceID == ChannelInGroup::wholeGroup)
{
if (const auto* state = host->getProfileStates().groupStates[output->getIncomingGroup()].getStateForDestination (output->getIncomingHeader().deviceID))
{
const auto active = state->getActive();
const auto inactive = state->getInactive();
detail::MessageTypeUtils::send (*output, Message::ProfileInquiryResponse { active, inactive });
}
}
else if (output->getIncomingHeader().deviceID == ChannelInGroup::wholeBlock)
{
auto header = output->getReplyHeader (detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2);
const auto sendIfNonEmpty = [&] (const auto group, const auto& state)
{
if (! state.empty())
{
const auto active = state.getActive();
const auto inactive = state.getInactive();
detail::MessageTypeUtils::send (*output, (uint8_t) group, header, Message::ProfileInquiryResponse { active, inactive });
}
};
for (auto groupNum = 0; groupNum < host->functionBlock.numGroups; ++groupNum)
{
const auto group = host->functionBlock.firstGroup + groupNum;
const auto& groupState = host->getProfileStates().groupStates[(size_t) group];
for (size_t channel = 0; channel < groupState.channelStates.size(); ++channel)
{
header.deviceID = ChannelInGroup (channel);
sendIfNonEmpty (group, groupState.channelStates[channel]);
}
}
header.deviceID = ChannelInGroup::wholeGroup;
for (auto i = 0; i < host->functionBlock.numGroups; ++i)
{
const auto group = host->functionBlock.firstGroup + i;
const auto& groupState = host->getProfileStates().groupStates[(size_t) group];
sendIfNonEmpty (group, groupState.groupState);
}
// Always send the block response to indicate that no further replies will follow
header.deviceID = ChannelInGroup::wholeBlock;
const auto state = host->getProfileStates().blockState;
const auto active = state.getActive();
const auto inactive = state.getInactive();
detail::MessageTypeUtils::send (*output, output->getIncomingGroup(), header, Message::ProfileInquiryResponse { active, inactive });
}
return true;
}
bool messageReceived (const Message::ProfileDetails& body) const
{
if (body.target == std::byte{})
{
const auto address = ChannelAddress{}.withGroup (output->getIncomingGroup())
.withChannel (output->getIncomingHeader().deviceID);
const ProfileAtAddress profileAtAddress { body.profile, address };
const auto state = host->getState (profileAtAddress);
std::vector<std::byte> extraData;
detail::Marshalling::Writer { extraData } (state.active, state.supported);
detail::MessageTypeUtils::send (*output, Message::ProfileDetailsResponse { body.profile, body.target, extraData });
}
else
{
detail::MessageTypeUtils::sendNAK (*output, std::byte { 0x04 });
}
return true;
}
template <typename Body>
bool profileEnablementReceived (const Body& request) const
{
const auto destination = ChannelAddress{}.withGroup (output->getIncomingGroup())
.withChannel (output->getIncomingHeader().deviceID);
if (auto* state = host->states.getStateForDestination (destination))
{
if (state->get (request.profile).isSupported())
{
const auto address = ChannelAddress{}.withGroup (output->getIncomingGroup())
.withChannel (output->getIncomingHeader().deviceID);
const ProfileAtAddress profileAtAddress { request.profile, address };
{
const ScopedValueSetter scope { host->currentEnablementMessage,
std::optional<ProfileAtAddress> (profileAtAddress) };
host->delegate.profileEnablementRequested (output->getIncomingHeader().source,
profileAtAddress,
getNumChannels (output->getIncomingHeader(), request),
std::is_same_v<Message::ProfileOn, Body>);
}
const auto currentState = host->getState (profileAtAddress);
const auto sendResponse = [&] (auto response)
{
const Message::Header header
{
profileAtAddress.address.getChannel(),
detail::MessageMeta::Meta<decltype (response)>::subID2,
detail::MessageMeta::implementationVersion,
output->getMuid(),
MUID::getBroadcast(),
};
detail::MessageTypeUtils::send (*output, profileAtAddress.address.getGroup(), header, response);
};
if (currentState.isActive())
sendResponse (Message::ProfileEnabledReport { profileAtAddress.profile, currentState.active });
else
sendResponse (Message::ProfileDisabledReport { profileAtAddress.profile, 0 });
host->isResponder = true;
return true;
}
}
detail::MessageTypeUtils::sendNAK (*output, {});
return true;
}
bool messageReceived (const Message::ProfileOn& request) const
{
return profileEnablementReceived (request);
}
bool messageReceived (const Message::ProfileOff& request) const
{
return profileEnablementReceived (request);
}
ProfileHost* host = nullptr;
ResponderOutput* output = nullptr;
bool* handled = nullptr;
};
void ProfileHost::addProfile (ProfileAtAddress profileAtAddress, int maxNumChannels)
{
auto* state = states.getStateForDestination (profileAtAddress.address);
if (state == nullptr || state->get (profileAtAddress.profile).isSupported())
return;
// There are only 256 channels on a UMP endpoint, so requesting more probably doesn't make sense!
jassert (maxNumChannels <= 256);
state->set (profileAtAddress.profile, { (uint16_t) maxNumChannels, 0 });
if (! isResponder || profileAtAddress == currentEnablementMessage)
return;
const Message::Header header
{
profileAtAddress.address.getChannel(),
detail::MessageMeta::Meta<Message::ProfileAdded>::subID2,
detail::MessageMeta::implementationVersion,
output.getMuid(),
MUID::getBroadcast(),
};
detail::MessageTypeUtils::send (output,
profileAtAddress.address.getGroup(),
header,
Message::ProfileAdded { profileAtAddress.profile });
}
void ProfileHost::removeProfile (ProfileAtAddress profileAtAddress)
{
auto* state = states.getStateForDestination (profileAtAddress.address);
if (state == nullptr)
return;
disableProfile (profileAtAddress);
if (! state->get (profileAtAddress.profile).isSupported())
return;
state->erase (profileAtAddress.profile);
if (! isResponder || profileAtAddress == currentEnablementMessage)
return;
const Message::Header header
{
profileAtAddress.address.getChannel(),
detail::MessageMeta::Meta<Message::ProfileRemoved>::subID2,
detail::MessageMeta::implementationVersion,
output.getMuid(),
MUID::getBroadcast(),
};
detail::MessageTypeUtils::send (output,
profileAtAddress.address.getGroup(),
header,
Message::ProfileRemoved { profileAtAddress.profile });
}
void ProfileHost::enableProfile (ProfileAtAddress profileAtAddress, int numChannels)
{
auto* state = states.getStateForDestination (profileAtAddress.address);
if (state == nullptr)
return;
const auto old = state->get (profileAtAddress.profile);
if (! old.isSupported())
return;
// There are only 256 channels on a UMP endpoint, so requesting more probably doesn't make sense!
jassert (numChannels <= 256);
const auto enabledChannels = jmin (old.supported, (uint16_t) numChannels);
state->set (profileAtAddress.profile, { old.supported, enabledChannels });
if (! isResponder || profileAtAddress == currentEnablementMessage)
return;
const Message::Header header
{
profileAtAddress.address.getChannel(),
detail::MessageMeta::Meta<Message::ProfileEnabledReport>::subID2,
detail::MessageMeta::implementationVersion,
output.getMuid(),
MUID::getBroadcast(),
};
detail::MessageTypeUtils::send (output,
profileAtAddress.address.getGroup(),
header,
Message::ProfileEnabledReport { profileAtAddress.profile, enabledChannels });
}
void ProfileHost::disableProfile (ProfileAtAddress profileAtAddress)
{
auto* state = states.getStateForDestination (profileAtAddress.address);
if (state == nullptr)
return;
const auto old = state->get (profileAtAddress.profile);
if (! old.isActive())
return;
state->set (profileAtAddress.profile, { old.supported, 0 });
if (! isResponder || profileAtAddress == currentEnablementMessage)
return;
const Message::Header header
{
profileAtAddress.address.getChannel(),
detail::MessageMeta::Meta<Message::ProfileDisabledReport>::subID2,
detail::MessageMeta::implementationVersion,
output.getMuid(),
MUID::getBroadcast(),
};
detail::MessageTypeUtils::send (output,
profileAtAddress.address.getGroup(),
header,
Message::ProfileDisabledReport { profileAtAddress.profile, old.active });
}
bool ProfileHost::tryRespond (ResponderOutput& responderOutput, const Message::Parsed& message)
{
bool result = false;
detail::MessageTypeUtils::visit (message, Visitor { this, &responderOutput, &result });
return result;
}
} // namespace juce::midi_ci

View file

@ -0,0 +1,112 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Acting as a ResponderListener, instances of this class can formulate
appropriate replies to profile transactions initiated by remote devices.
ProfileHost instances also contains methods to inform remote devices about
changes to local profile state.
Stores the current state of profiles on the local device.
@tags{Audio}
*/
class ProfileHost final : public ResponderDelegate
{
public:
/** @internal
Rather than constructing one of these objects yourself, you should configure
a Device with profile support, and then use Device::getProfileHost()
to retrieve a profile host that has been set up to work with that device.
*/
ProfileHost (FunctionBlock fb, ProfileDelegate& d, BufferOutput& o)
: functionBlock (fb), delegate (d), output (o) {}
/** Adds support for a profile on the specified group/channel with a
maximum number of channels that may be activated.
*/
void addProfile (ProfileAtAddress, int maxNumChannels = 1);
/** Removes support for a profile on the specified group/channel.
*/
void removeProfile (ProfileAtAddress);
/** Activates a profile on the specified group/channel with the provided
number of channels.
The profile should previously have been added with addProfile(), and
numChannels should be in the closed range between 1 and the maximum
number of channels allowed for that profile.
*/
void enableProfile (ProfileAtAddress, int numChannels);
/** Deactivates a profile on the specified group/channel.
*/
void disableProfile (ProfileAtAddress);
/** Returns the profile states (supported/active) for all groups and channels.
*/
const BlockProfileStates& getProfileStates() const { return states; }
/** Returns the number of supported and active channels for the given
profile on the specified group/channel.
If the supported channels is 0, then the profile is not supported
on the group/channel.
If the active channels is 0, then the profile is inactive on the
group/channel.
*/
SupportedAndActive getState (ProfileAtAddress profileAtAddress) const
{
if (auto* state = states.getStateForDestination (profileAtAddress.address))
return state->get (profileAtAddress.profile);
return {};
}
/** @internal */
bool tryRespond (ResponderOutput&, const Message::Parsed&) override;
private:
class Visitor;
template <typename Body>
bool profileEnablementReceived (ResponderOutput&, const Body&);
FunctionBlock functionBlock;
ProfileDelegate& delegate;
BufferOutput& output;
BlockProfileStates states;
bool isResponder = false;
std::optional<ProfileAtAddress> currentEnablementMessage;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,86 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
SupportedAndActive ChannelProfileStates::get (const Profile& profile) const
{
const auto iter = std::lower_bound (entries.begin(), entries.end(), profile);
if (iter != entries.end() && iter->profile == profile)
return iter->state;
return {};
}
std::vector<Profile> ChannelProfileStates::getActive() const
{
std::vector<Profile> result;
for (const auto& item : entries)
if (item.state.isActive())
result.push_back (item.profile);
return result;
}
std::vector<Profile> ChannelProfileStates::getInactive() const
{
std::vector<Profile> result;
for (const auto& item : entries)
if (item.state.isSupported())
result.push_back (item.profile);
return result;
}
void ChannelProfileStates::set (const Profile& profile, SupportedAndActive state)
{
const auto iter = std::lower_bound (entries.begin(), entries.end(), profile);
if (iter != entries.end() && iter->profile == profile)
{
if (state != SupportedAndActive{})
iter->state = state;
else
entries.erase (iter);
}
else if (state != SupportedAndActive{})
{
entries.insert (iter, { profile, state });
}
}
void ChannelProfileStates::erase (const Profile& profile)
{
const auto iter = std::lower_bound (entries.begin(), entries.end(), profile);
if (iter != entries.end() && iter->profile == profile)
entries.erase (iter);
}
} // namespace juce::midi_ci

View file

@ -0,0 +1,161 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Holds a profile ID, along with the number of supported and active channels
corresponding to that profile.
@tags{Audio}
*/
struct ProfileStateEntry
{
Profile profile; ///< A MIDI-CI profile ID
SupportedAndActive state; ///< The number of channels corresponding to the profile
bool operator< (const Profile& other) const { return profile < other; }
bool operator< (const ProfileStateEntry& other) const { return profile < other.profile; }
};
//==============================================================================
/**
Holds the number of channels that are supported and activated for all profiles
at a particular channel address.
@tags{Audio}
*/
class ChannelProfileStates
{
public:
using Entry = ProfileStateEntry;
/** Returns the number of channels that are supported and active for the
given profile.
*/
SupportedAndActive get (const Profile& profile) const;
/** Returns all profiles that are active at this address. */
std::vector<Profile> getActive() const;
/** Returns all profiles that are supported but inactive at this address. */
std::vector<Profile> getInactive() const;
/** Sets the number of channels that are supported/active for a given profile. */
void set (const Profile& profile, SupportedAndActive state);
/** Removes the record of a particular profile, equivalent to removing support. */
void erase (const Profile& profile);
/** Gets a const iterator over all profiles, for range-for compatibility. */
auto begin() const { return entries.begin(); }
/** Gets a const iterator over all profiles, for range-for compatibility. */
auto end() const { return entries.end(); }
/** Returns true if no profiles are supported. */
auto empty() const { return entries.empty(); }
/** Returns the number of profiles that are supported at this address. */
auto size() const { return entries.size(); }
private:
std::vector<Entry> entries;
};
//==============================================================================
/**
Contains profile states for each channel in a group, along with the state
of profiles that apply to the group itself.
@tags{Audio}
*/
class GroupProfileStates
{
template <typename This>
static auto getStateForDestinationImpl (This& t, ChannelInGroup destination) -> decltype (&t.groupState)
{
if (destination == ChannelInGroup::wholeGroup)
return &t.groupState;
if (const auto index = (size_t) destination; index < t.channelStates.size())
return &t.channelStates[index];
return nullptr;
}
public:
/** Returns the profile state for the group or a contained channel as appropriate.
Returns nullptr if ChannelInGroup refers to a whole function block.
*/
auto* getStateForDestination (ChannelInGroup d) { return getStateForDestinationImpl (*this, d); }
/** Returns the profile state for the group or a contained channel as appropriate.
Returns nullptr if ChannelInGroup refers to a whole function block.
*/
auto* getStateForDestination (ChannelInGroup d) const { return getStateForDestinationImpl (*this, d); }
std::array<ChannelProfileStates, 16> channelStates; ///< Profile states for each channel in the group
ChannelProfileStates groupState; ///< Profile states for the group itself
};
//==============================================================================
/**
Contains profile states for each group and channel in a function block, along with the state
of profiles that apply to the function block itself.
@tags{Audio}
*/
class BlockProfileStates
{
template <typename This>
static auto getStateForDestinationImpl (This& t, ChannelAddress address) -> decltype (&t.blockState)
{
if (address.isBlock())
return &t.blockState;
if (const auto index = (size_t) address.getGroup(); index < t.groupStates.size())
return t.groupStates[index].getStateForDestination (address.getChannel());
return nullptr;
}
public:
/** Returns the profile state for the function block, group, or channel as appropriate.
Returns nullptr if the address refers to a non-existent channel or group.
*/
auto* getStateForDestination (ChannelAddress address) { return getStateForDestinationImpl (*this, address); }
/** Returns the profile state for the function block, group, or channel as appropriate.
Returns nullptr if the address refers to a non-existent channel or group.
*/
auto* getStateForDestination (ChannelAddress address) const { return getStateForDestinationImpl (*this, address); }
std::array<GroupProfileStates, 16> groupStates; ///< Profile states for each group in the function block
ChannelProfileStates blockState; ///< Profile states for the whole function block
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,255 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
struct PropertyDelegateDetail
{
/*
Note: We don't use ToVar and FromVar here, because we want to omit fields that are using
their default values.
*/
template <typename Target>
static Target parseTargetHeader (const var& v,
const std::map<Identifier, void (*) (Target&, const var&)>& parsers)
{
Target target;
if (auto* obj = v.getDynamicObject())
{
for (const auto& pair : obj->getProperties())
{
const auto parserIter = parsers.find (pair.name);
if (parserIter != parsers.end())
parserIter->second (target, pair.value);
else
target.extended[pair.name] = pair.value;
}
}
return target;
}
static auto getParsersForPropertyReplyHeader()
{
using Target = PropertyReplyHeader;
std::map<Identifier, void (*) (Target& header, const var& v)> map;
map.emplace ("status", [] (Target& header, const var& v) { header.status = v; });
map.emplace ("message", [] (Target& header, const var& v) { header.message = v; });
map.emplace ("cacheTime", [] (Target& header, const var& v) { header.cacheTime = v; });
map.emplace ("mediaType", [] (Target& header, const var& v) { header.mediaType = v; });
map.emplace ("mutualEncoding", [] (Target& header, const var& v)
{
header.mutualEncoding = EncodingUtils::toEncoding (v.toString().toRawUTF8()).value_or (Encoding::ascii);
});
return map;
}
template <typename Target>
static auto getParsersForGenericPropertyRequestHeader()
{
std::map<Identifier, void (*) (Target& header, const var& v)> map;
map.emplace ("resource", [] (Target& header, const var& v) { header.resource = v; });
map.emplace ("resId", [] (Target& header, const var& v) { header.resId = v; });
map.emplace ("mediaType", [] (Target& header, const var& v) { header.mediaType = v; });
map.emplace ("mutualEncoding", [] (Target& header, const var& v)
{
header.mutualEncoding = EncodingUtils::toEncoding (v.toString().toRawUTF8()).value_or (Encoding::ascii);
});
return map;
}
static auto getParsersForPropertyRequestHeader()
{
auto map = getParsersForGenericPropertyRequestHeader<PropertyRequestHeader>();
map.emplace ("setPartial", [] (PropertyRequestHeader& header, const var& v) { header.setPartial = v; });
map.emplace ("offset", [] (PropertyRequestHeader& header, const var& v)
{
if (! header.pagination.has_value())
header.pagination = Pagination{};
header.pagination->offset = v;
});
map.emplace ("limit", [] (PropertyRequestHeader& header, const var& v)
{
if (! header.pagination.has_value())
header.pagination = Pagination{};
header.pagination->limit = v;
});
return map;
}
static auto getParsersForPropertySubscriptionHeader()
{
auto map = getParsersForGenericPropertyRequestHeader<PropertySubscriptionHeader>();
map.emplace ("subscribeId", [] (PropertySubscriptionHeader& header, const var& v) { header.subscribeId = v; });
map.emplace ("command", [] (PropertySubscriptionHeader& header, const var& v)
{
header.command = [&]
{
if (v == "start")
return PropertySubscriptionCommand::start;
if (v == "partial")
return PropertySubscriptionCommand::partial;
if (v == "full")
return PropertySubscriptionCommand::full;
if (v == "notify")
return PropertySubscriptionCommand::notify;
if (v == "end")
return PropertySubscriptionCommand::end;
return PropertySubscriptionCommand::notify;
}();
});
return map;
}
static auto getSetPartial (const PropertySubscriptionHeader&) { return false; }
static auto getSetPartial (const PropertyRequestHeader& h) { return h.setPartial; }
static auto getSetPartial (const PropertyReplyHeader&) { return false; }
static auto getPagination (const PropertySubscriptionHeader&) { return std::optional<Pagination>{}; }
static auto getPagination (const PropertyRequestHeader& h) { return h.pagination; }
static auto getPagination (const PropertyReplyHeader&) { return std::optional<Pagination>{}; }
static auto getCacheTime (const PropertySubscriptionHeader&) { return 0; }
static auto getCacheTime (const PropertyRequestHeader&) { return 0; }
static auto getCacheTime (const PropertyReplyHeader& h) { return h.cacheTime; }
static auto getMessage (const PropertySubscriptionHeader&) { return String{}; }
static auto getMessage (const PropertyRequestHeader&) { return String{}; }
static auto getMessage (const PropertyReplyHeader& h) { return h.message; }
static auto getResource (const PropertySubscriptionHeader& h) { return h.resource; }
static auto getResource (const PropertyRequestHeader& h) { return h.resource; }
static auto getResource (const PropertyReplyHeader&) { return String{}; }
static auto getResId (const PropertySubscriptionHeader& h) { return h.resId; }
static auto getResId (const PropertyRequestHeader& h) { return h.resId; }
static auto getResId (const PropertyReplyHeader&) { return String{}; }
static auto getCommand (const PropertySubscriptionHeader& h) { return h.command; }
static auto getCommand (const PropertyRequestHeader&) { return PropertySubscriptionCommand{}; }
static auto getCommand (const PropertyReplyHeader&) { return PropertySubscriptionCommand{}; }
static auto getSubscribeId (const PropertySubscriptionHeader& h) { return h.subscribeId; }
static auto getSubscribeId (const PropertyRequestHeader&) { return String{}; }
static auto getSubscribeId (const PropertyReplyHeader&) { return String{}; }
static auto getStatus (const PropertySubscriptionHeader&) { return 0; }
static auto getStatus (const PropertyRequestHeader&) { return 0; }
static auto getStatus (const PropertyReplyHeader& h) { return h.status; }
template <typename T>
static auto toFieldsFromHeader (const T& t)
{
auto fields = t.extended;
if (getResource (t) != getResource (T()))
fields["resource"] = getResource (t);
if (getCommand (t) != getCommand (T()))
fields["command"] = PropertySubscriptionCommandUtils::toString (getCommand (t));
if (getSubscribeId (t) != getSubscribeId (T()))
fields["subscribeId"] = getSubscribeId (t);
if (getResId (t) != getResId (T()))
fields["resId"] = getResId (t);
if (t.mutualEncoding != T().mutualEncoding)
fields["mutualEncoding"] = EncodingUtils::toString (t.mutualEncoding);
if (t.mediaType != T().mediaType)
fields["mediaType"] = t.mediaType;
if (getStatus (t) != getStatus (T()))
fields["status"] = getStatus (t);
if (getSetPartial (t))
fields["setPartial"] = true;
if (getCacheTime (t) != getCacheTime (T()))
fields["cacheTime"] = getCacheTime (t);
if (getMessage (t) != getMessage (T()))
fields["message"] = getMessage (t);
if (const auto pagination = getPagination (t))
{
fields["offset"] = pagination->offset;
fields["limit"] = pagination->limit;
}
return fields;
}
};
//==============================================================================
PropertySubscriptionHeader PropertySubscriptionHeader::parseCondensed (const var& v)
{
return PropertyDelegateDetail::parseTargetHeader (v, PropertyDelegateDetail::getParsersForPropertySubscriptionHeader());
}
var PropertySubscriptionHeader::toVarCondensed() const
{
return JSONUtils::makeObjectWithKeyFirst (PropertyDelegateDetail::toFieldsFromHeader (*this), "command");
}
PropertyRequestHeader PropertyRequestHeader::parseCondensed (const var& v)
{
return PropertyDelegateDetail::parseTargetHeader (v, PropertyDelegateDetail::getParsersForPropertyRequestHeader());
}
var PropertyRequestHeader::toVarCondensed() const
{
return JSONUtils::makeObjectWithKeyFirst (PropertyDelegateDetail::toFieldsFromHeader (*this), "resource");
}
PropertyReplyHeader PropertyReplyHeader::parseCondensed (const var& v)
{
return PropertyDelegateDetail::parseTargetHeader (v, PropertyDelegateDetail::getParsersForPropertyReplyHeader());
}
var PropertyReplyHeader::toVarCondensed() const
{
return JSONUtils::makeObjectWithKeyFirst (PropertyDelegateDetail::toFieldsFromHeader (*this), "status");
}
} // namespace juce::midi_ci

View file

@ -0,0 +1,270 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
#define JUCE_SUBSCRIPTION_COMMANDS X(start) X(partial) X(full) X(notify) X(end)
/**
Kinds of command that may be sent as part of a subscription update.
Check the Property Exchange specification to find the meaning of the
different kinds.
@tags{Audio}
*/
enum class PropertySubscriptionCommand
{
#define X(str) str,
JUCE_SUBSCRIPTION_COMMANDS
#undef X
};
struct PropertySubscriptionCommandUtils
{
PropertySubscriptionCommandUtils() = delete;
/** Converts a command to a human-readable string. */
static const char* toString (PropertySubscriptionCommand x)
{
switch (x)
{
#define X(str) case PropertySubscriptionCommand::str: return #str;
JUCE_SUBSCRIPTION_COMMANDS
#undef X
}
return nullptr;
}
/** Converts a command string from a property exchange JSON header to
an PropertySubscriptionCommand.
*/
static std::optional<PropertySubscriptionCommand> toCommand (const char* str)
{
#define X(name) if (std::string_view (str) == std::string_view (#name)) return PropertySubscriptionCommand::name;
JUCE_SUBSCRIPTION_COMMANDS
#undef X
return {};
}
};
#undef JUCE_SUBSCRIPTION_COMMANDS
/**
A struct containing data members that correspond to common fields in a
property subscription header.
Check the Property Exchange specification to find the meaning of the
different fields.
@tags{Audio}
*/
struct PropertySubscriptionHeader
{
String resource;
String resId;
Encoding mutualEncoding = Encoding::ascii;
String mediaType = "application/json";
PropertySubscriptionCommand command { -1 };
String subscribeId;
std::map<Identifier, var> extended;
/** Converts a JSON object to a PropertyRequestHeader.
Unspecified fields will use their default values.
*/
static PropertySubscriptionHeader parseCondensed (const var&);
/** Converts a PropertySubscriptionHeader to a JSON object suitable for use as
a MIDI-CI message header after conversion to 7-bit ASCII.
*/
var toVarCondensed() const;
};
/**
Contains information about the pagination of a request.
Check the Property Exchange specification to find the meaning of the
different fields.
@tags{Audio}
*/
struct Pagination
{
int offset = 0;
int limit = 1;
};
/**
A struct containing data members that correspond to common fields in a
property request header.
Check the Property Exchange specification to find the meaning of the
different fields.
@tags{Audio}
*/
struct PropertyRequestHeader
{
String resource;
String resId;
Encoding mutualEncoding = Encoding::ascii;
String mediaType = "application/json";
bool setPartial = false;
std::optional<Pagination> pagination;
std::map<Identifier, var> extended;
/** Converts a JSON object to a PropertyRequestHeader.
Unspecified fields will use their default values.
*/
static PropertyRequestHeader parseCondensed (const var&);
/** Converts a PropertyRequestHeader to a JSON object suitable for use as
a MIDI-CI message header after conversion to 7-bit ASCII.
*/
var toVarCondensed() const;
};
/**
Bundles together a property request header and a data payload.
@tags{Audio}
*/
struct PropertyRequestData
{
PropertyRequestHeader header;
Span<const std::byte> body;
};
/**
A struct containing data members that correspond to common fields in a
reply to a property exchange request.
Check the Property Exchange specification to find the meaning of the
different fields.
For extended attributes that don't correspond to any of the defined data
members, use the 'extended' map.
@tags{Audio}
*/
struct PropertyReplyHeader
{
int status = 200;
String message;
Encoding mutualEncoding = Encoding::ascii;
int cacheTime = 0;
String mediaType = "application/json";
std::map<Identifier, var> extended;
/** Converts a JSON object to a PropertyReplyHeader.
Unspecified fields will use their default values.
*/
static PropertyReplyHeader parseCondensed (const var&);
/** Converts a PropertyReplyHeader to a JSON object suitable for use as
a MIDI-CI message header after conversion to 7-bit ASCII.
*/
var toVarCondensed() const;
};
/**
Bundles together a property reply header and a data payload.
@tags{Audio}
*/
struct PropertyReplyData
{
PropertyReplyHeader header;
std::vector<std::byte> body;
};
/**
An interface with methods that can be overridden to customise how a Device
implementing properties responds to property inquiries.
@tags{Audio}
*/
struct PropertyDelegate
{
PropertyDelegate() = default;
virtual ~PropertyDelegate() = default;
PropertyDelegate (const PropertyDelegate&) = default;
PropertyDelegate (PropertyDelegate&&) = default;
PropertyDelegate& operator= (const PropertyDelegate&) = default;
PropertyDelegate& operator= (PropertyDelegate&&) = default;
/** Returns the max number of simultaneous property exchange messages that can be processed. */
virtual uint8_t getNumSimultaneousRequestsSupported() const { return 127; }
/** Returns a header/body containing the requested data.
To report an error, you can return a failure status code in the header and leave the body empty.
*/
virtual PropertyReplyData propertyGetDataRequested (MUID, const PropertyRequestHeader&) = 0;
/** Returns a header that describes the result of the set operation. */
virtual PropertyReplyHeader propertySetDataRequested (MUID, const PropertyRequestData&) = 0;
/** Returns true to allow the subscription, or false otherwise. */
virtual bool subscriptionStartRequested (MUID, const PropertySubscriptionHeader&) = 0;
/** Called with the corresponding subscription token after a subscription has started. */
virtual void subscriptionDidStart (MUID, const String& subId, const PropertySubscriptionHeader&) = 0;
/** Called when a device requests for an ongoing subscription to end. */
virtual void subscriptionWillEnd (MUID, const Subscription& sub) = 0;
};
} // namespace juce::midi_ci
namespace juce
{
template <>
struct SerialisationTraits<midi_ci::PropertySubscriptionCommand>
{
static constexpr auto marshallingVersion = std::nullopt;
template <typename Archive>
void load (Archive& archive, midi_ci::PropertySubscriptionCommand& t)
{
String command;
archive (command);
t = midi_ci::PropertySubscriptionCommandUtils::toCommand (command.toRawUTF8()).value_or (midi_ci::PropertySubscriptionCommand{});
}
template <typename Archive>
void save (Archive& archive, const midi_ci::PropertySubscriptionCommand& t)
{
archive (midi_ci::PropertySubscriptionCommandUtils::toString (t));
}
};
} // namespace juce

View file

@ -0,0 +1,291 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
class PropertyExchangeCache
{
public:
explicit PropertyExchangeCache (std::function<void()> term)
: onTerminate (std::move (term)) {}
struct OwningResult
{
explicit OwningResult (PropertyExchangeResult::Error e)
: result (e) {}
OwningResult (var header, std::vector<std::byte> body)
: backingStorage (std::move (body)),
result (header, backingStorage) {}
OwningResult (OwningResult&&) noexcept = default;
OwningResult& operator= (OwningResult&&) noexcept = default;
JUCE_DECLARE_NON_COPYABLE (OwningResult)
std::vector<std::byte> backingStorage;
PropertyExchangeResult result;
};
std::optional<OwningResult> addChunk (Message::DynamicSizePropertyExchange chunk)
{
jassert (chunk.thisChunkNum == lastChunk + 1 || chunk.thisChunkNum == 0);
lastChunk = chunk.thisChunkNum;
headerStorage.reserve (headerStorage.size() + chunk.header.size());
std::transform (chunk.header.begin(),
chunk.header.end(),
std::back_inserter (headerStorage),
[] (std::byte b) { return char (b); });
bodyStorage.insert (bodyStorage.end(), chunk.data.begin(), chunk.data.end());
if (chunk.thisChunkNum != 0 && chunk.thisChunkNum != chunk.totalNumChunks)
return {};
const auto headerJson = JSON::parse (String (headerStorage.data(), headerStorage.size()));
onTerminate = nullptr;
const auto encodingString = headerJson.getProperty ("mutualEncoding", "ASCII").toString();
if (chunk.thisChunkNum != chunk.totalNumChunks)
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::partial };
return std::optional<OwningResult> { std::in_place,
headerJson,
Encodings::decode (bodyStorage, EncodingUtils::toEncoding (encodingString.toRawUTF8()).value_or (Encoding::ascii)) };
}
std::optional<OwningResult> notify (Span<const std::byte> header)
{
const auto headerJson = JSON::parse (String (reinterpret_cast<const char*> (header.data()), header.size()));
if (! headerJson.isObject())
return {};
const auto status = headerJson.getProperty ("status", {});
if (! status.isInt() || (int) status == 100)
return {};
onTerminate = nullptr;
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::notify };
}
void terminate()
{
if (auto t = std::exchange (onTerminate, nullptr))
t();
}
private:
std::vector<char> headerStorage;
std::vector<std::byte> bodyStorage;
std::function<void()> onTerminate;
uint16_t lastChunk = 0;
};
//==============================================================================
class PropertyExchangeCacheArray
{
public:
PropertyExchangeCacheArray() = default;
ErasedScopeGuard primeCacheForRequestId (std::byte id,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void()> onTerminate)
{
auto& entry = caches[(uint8_t) id];
entry = std::make_shared<Transaction> (std::move (onDone), std::move (onTerminate));
auto weak = std::weak_ptr<Transaction> (entry);
return ErasedScopeGuard { [&entry, weak]
{
// If this fails, then the transaction finished before the ErasedScopeGuard was destroyed.
if (auto locked = weak.lock())
{
entry->cache.terminate();
entry = nullptr;
}
} };
}
void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk)
{
updateCache (b, [&] (PropertyExchangeCache& c) { return c.addChunk (chunk); });
}
void notify (std::byte b, Span<const std::byte> header)
{
updateCache (b, [&] (PropertyExchangeCache& c) { return c.notify (header); });
}
bool hasTransaction (std::byte id) const
{
return caches[(uint8_t) id] != nullptr;
}
uint8_t countOngoingTransactions() const
{
return (uint8_t) std::count_if (caches.begin(), caches.end(), [] (auto& c) { return c != nullptr; });
}
/** MSB of result is set on failure. */
std::byte findUnusedId (uint8_t maxSimultaneousTransactions) const
{
if (countOngoingTransactions() >= maxSimultaneousTransactions)
return std::byte { 0xff };
return (std::byte) std::distance (caches.begin(), std::find (caches.begin(), caches.end(), nullptr));
}
// Instances must stay at the same location to ensure that references captured in the
// ErasedScopeGuard returned from primeCacheForRequestId do not dangle.
JUCE_DECLARE_NON_COPYABLE (PropertyExchangeCacheArray)
JUCE_DECLARE_NON_MOVEABLE (PropertyExchangeCacheArray)
private:
static constexpr auto numCaches = 128;
class Transaction
{
public:
Transaction (std::function<void (const PropertyExchangeResult&)> onSuccess,
std::function<void()> onTerminate)
: cache (std::move (onTerminate)), onFinish (std::move (onSuccess)) {}
PropertyExchangeCache cache;
std::function<void (const PropertyExchangeResult&)> onFinish;
};
template <typename WithCache>
void updateCache (std::byte b, WithCache&& withCache)
{
if (auto& entry = caches[(uint8_t) b])
{
if (const auto result = withCache (entry->cache))
{
const auto tmp = std::move (entry->onFinish);
entry = nullptr;
NullCheckedInvocation::invoke (tmp, result->result);
}
}
}
std::array<std::shared_ptr<Transaction>, numCaches> caches;
};
//==============================================================================
class InitiatorPropertyExchangeCache::Impl
{
public:
TokenAndId primeCache (uint8_t maxSimultaneousRequests,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void (std::byte)> onTerminate)
{
const auto id = array.findUnusedId (maxSimultaneousRequests);
if ((id & std::byte { 0x80 }) != std::byte{})
{
NullCheckedInvocation::invoke (onDone, PropertyExchangeResult { PropertyExchangeResult::Error::tooManyTransactions });
return {};
}
auto token = array.primeCacheForRequestId (id,
std::move (onDone),
[id, term = std::move (onTerminate)] { NullCheckedInvocation::invoke (term, id); });
return { std::move (token), id };
}
void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); }
void notify (std::byte b, Span<const std::byte> header) { array.notify (b, header); }
int countOngoingTransactions() const { return array.countOngoingTransactions(); }
bool isAwaitingResponse() const { return countOngoingTransactions() != 0; }
private:
PropertyExchangeCacheArray array;
};
//==============================================================================
InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache() : pimpl (std::make_unique<Impl>()) {}
InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept = default;
InitiatorPropertyExchangeCache& InitiatorPropertyExchangeCache::operator= (InitiatorPropertyExchangeCache&&) noexcept = default;
InitiatorPropertyExchangeCache::~InitiatorPropertyExchangeCache() = default;
InitiatorPropertyExchangeCache::TokenAndId InitiatorPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void (std::byte)> onTerminate)
{
return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone), std::move (onTerminate));
}
void InitiatorPropertyExchangeCache::addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); }
void InitiatorPropertyExchangeCache::notify (std::byte b, Span<const std::byte> header) { pimpl->notify (b, header); }
int InitiatorPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); }
bool InitiatorPropertyExchangeCache::isAwaitingResponse() const { return pimpl->isAwaitingResponse(); }
//==============================================================================
class ResponderPropertyExchangeCache::Impl
{
public:
void primeCache (uint8_t maxSimultaneousTransactions,
std::function<void (const PropertyExchangeResult&)> onDone,
std::byte id)
{
if (array.hasTransaction (id))
return;
if (array.countOngoingTransactions() >= maxSimultaneousTransactions)
NullCheckedInvocation::invoke (onDone, PropertyExchangeResult { PropertyExchangeResult::Error::tooManyTransactions });
else
array.primeCacheForRequestId (id, std::move (onDone), nullptr).release();
}
void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); }
void notify (std::byte b, Span<const std::byte> header) { array.notify (b, header); }
int countOngoingTransactions() const { return array.countOngoingTransactions(); }
private:
PropertyExchangeCacheArray array;
};
//==============================================================================
ResponderPropertyExchangeCache::ResponderPropertyExchangeCache() : pimpl (std::make_unique<Impl>()) {}
ResponderPropertyExchangeCache::ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept = default;
ResponderPropertyExchangeCache& ResponderPropertyExchangeCache::operator= (ResponderPropertyExchangeCache&&) noexcept = default;
ResponderPropertyExchangeCache::~ResponderPropertyExchangeCache() = default;
void ResponderPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
std::function<void (const PropertyExchangeResult&)> onDone,
std::byte id)
{
return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone), id);
}
void ResponderPropertyExchangeCache::addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); }
void ResponderPropertyExchangeCache::notify (std::byte b, Span<const std::byte> header) { pimpl->notify (b, header); }
int ResponderPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); }
} // namespace juce::midi_ci

View file

@ -0,0 +1,158 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Accumulates message chunks that have been sent by another device in response
to a transaction initiated by a local device.
@tags{Audio}
*/
class InitiatorPropertyExchangeCache
{
public:
InitiatorPropertyExchangeCache();
~InitiatorPropertyExchangeCache();
InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept;
InitiatorPropertyExchangeCache& operator= (InitiatorPropertyExchangeCache&&) noexcept;
JUCE_DECLARE_NON_COPYABLE (InitiatorPropertyExchangeCache)
/** Holds a token that can be used to stop waiting for a reply, along with
an identifier byte which uniquely identifies an ongoing transaction.
*/
struct TokenAndId
{
TokenAndId() = default;
TokenAndId (ErasedScopeGuard tokenIn, std::byte idIn)
: token (std::move (tokenIn)), id (idIn) {}
bool isValid() const { return (id & std::byte { 0x80 }) == std::byte{}; }
ErasedScopeGuard token{};
std::byte id { 0x80 };
};
/** Picks an unused request ID, and prepares the cache for that ID to accumulate message chunks.
Incoming chunks added with addChunk are generated by another device acting as a responder.
*/
TokenAndId primeCache (uint8_t maxSimultaneousRequests,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void (std::byte)> onTerminate);
/** Adds a message chunk for the provided transaction id. */
void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk);
/** Updates the transaction state based on the contents of the provided notification. */
void notify (std::byte b, Span<const std::byte> header);
/** Returns the number of transactions that have been started but not finished. */
int countOngoingTransactions() const;
/** Returns true if there are any transactions in progress that
haven't yet received replies.
*/
bool isAwaitingResponse() const;
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
//==============================================================================
/**
Accumulates message chunks that form a request initiated by a remote device.
@tags{Audio}
*/
class ResponderPropertyExchangeCache
{
public:
ResponderPropertyExchangeCache();
~ResponderPropertyExchangeCache();
ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept;
ResponderPropertyExchangeCache& operator= (ResponderPropertyExchangeCache&&) noexcept;
JUCE_DECLARE_NON_COPYABLE (ResponderPropertyExchangeCache)
/** Prepares the cache for the given requestId to accumulate message chunks.
Incoming chunks added with addChunk are generated by another device acting as an initiator.
*/
void primeCache (uint8_t maxSimultaneousTransactions,
std::function<void (const PropertyExchangeResult&)> onDone,
std::byte id);
/** Adds a message chunk for the provided transaction id. */
void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk);
/** Updates the transaction state based on the contents of the provided notification. */
void notify (std::byte b, Span<const std::byte> header);
/** Returns the number of transactions that have been started but not finished. */
int countOngoingTransactions() const;
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
//==============================================================================
/**
An interface for objects that provide resources for property exchange
transactions.
@tags{Audio}
*/
class CacheProvider
{
public:
virtual ~CacheProvider() = default;
/** Returns a set containing all of the MUIDs currently known to the provider. */
virtual std::set<MUID> getDiscoveredMuids() const = 0;
/** Returns a property exchange cache for accumulating replies to transactions
we initiated.
*/
virtual InitiatorPropertyExchangeCache* getCacheForMuidAsInitiator (MUID m) = 0;
/** Returns a property exchange cache for accumulating requests initiated
by other devices.
*/
virtual ResponderPropertyExchangeCache* getCacheForMuidAsResponder (MUID m) = 0;
/** Returns the maximum sysex size supported by the device with the
given MUID.
*/
virtual int getMaxSysexSizeForMuid (MUID m) const = 0;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,118 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Contains data returned by a responder in response to a request.
PropertyExchangeResult::kind indicates whether the transaction resulted in
a well-formed message; however, it's possible that the message is a
well-formed message indicating an error in the responder, so it's important
to check the 'status' field of the header before attempting to do anything
with the payload.
@tags{Audio}
*/
class PropertyExchangeResult
{
public:
enum class Error
{
partial, ///< Got a response, but the responder terminated it before
///< sending a well-formed message.
notify, ///< Got a notify message terminating the transaction.
tooManyTransactions, ///< Unable to send the request because doing so would
///< exceed the number of simultaneous inquiries that were declared.
///< @see PropertyDelegate::getNumSimultaneousRequestsSupported().
invalidPayload, ///< The payload couldn't be encoded for transmission. If you're
///< using the ASCII encoding, maybe some bytes have their most
///< significant bit set.
};
/** Creates a result denoting an error state. */
explicit PropertyExchangeResult (Error errorIn)
: PropertyExchangeResult (errorIn, {}, {}) {}
/** Creates a result denoting a successful transmission. */
PropertyExchangeResult (var headerIn, Span<const std::byte> bodyIn)
: PropertyExchangeResult (std::nullopt, headerIn, bodyIn) {}
/** Returns the result kind, either nullopt for a successful transmission, or
an error code if something went wrong.
*/
std::optional<Error> getError() const { return error; }
/** Parses the header as a subscription header.
This may only be called for messages of kind 'full'.
*/
PropertySubscriptionHeader getHeaderAsSubscriptionHeader() const
{
jassert (header != var());
return PropertySubscriptionHeader::parseCondensed (header);
}
/** Parses the header as a request header.
This may only be called for messages of kind 'full'.
*/
PropertyRequestHeader getHeaderAsRequestHeader() const
{
jassert (header != var());
return PropertyRequestHeader::parseCondensed (header);
}
/** Parses the header as a reply header.
This may only be called for messages of kind 'full'.
*/
PropertyReplyHeader getHeaderAsReplyHeader() const
{
jassert (header != var());
return PropertyReplyHeader::parseCondensed (header);
}
/** When getKind returns 'full', this is the message payload.
Note that this is not stored internally; if you need to keep this data
around and reference it in the future, you should copy it into a
vector or some other suitable container.
*/
Span<const std::byte> getBody() const { return body; }
private:
PropertyExchangeResult (std::optional<Error> errorIn, var headerIn, Span<const std::byte> bodyIn)
: error (errorIn), header (headerIn), body (bodyIn) {}
std::optional<Error> error;
var header;
Span<const std::byte> body;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,411 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
class PropertyHost::Visitor : public detail::MessageTypeUtils::MessageVisitor
{
public:
Visitor (PropertyHost* h, ResponderOutput* o, bool* b)
: host (h), output (o), handled (b) {}
void visit (const Message::PropertyExchangeCapabilities& body) const override { visitImpl (body); }
void visit (const Message::PropertyGetData& body) const override { visitImpl (body); }
void visit (const Message::PropertySetData& body) const override { visitImpl (body); }
void visit (const Message::PropertySubscribe& body) const override { visitImpl (body); }
void visit (const Message::PropertyNotify& body) const override { visitImpl (body); }
using MessageVisitor::visit;
private:
template <typename Body>
void visitImpl (const Body& body) const { *handled = messageReceived (body); }
bool messageReceived (const Message::PropertyExchangeCapabilities&) const
{
detail::MessageTypeUtils::send (*output, Message::PropertyExchangeCapabilitiesResponse { std::byte { host->delegate.getNumSimultaneousRequestsSupported() },
{},
{} });
return true;
}
bool messageReceived (const Message::PropertyGetData& data) const
{
// This should always be a single message, so no need to accumulate chunks
const auto reply = host->delegate.propertyGetDataRequested (output->getIncomingHeader().source,
PropertyRequestHeader::parseCondensed (Encodings::jsonFrom7BitText (data.header)));
const auto encoded = Encodings::tryEncode (reply.body, reply.header.mutualEncoding);
if (! encoded.has_value())
{
// If this is hit, the data that was supplied isn't valid for the encoding that was specified
jassertfalse;
return false;
}
detail::PropertyHostUtils::send (*output,
output->getIncomingGroup(),
detail::MessageMeta::Meta<Message::PropertyGetDataResponse>::subID2,
output->getIncomingHeader().source,
data.requestID,
Encodings::jsonTo7BitText (reply.header.toVarCondensed()),
*encoded,
host->cacheProvider.getMaxSysexSizeForMuid (output->getIncomingHeader().source));
return true;
}
bool messageReceived (const Message::PropertySetData& data) const
{
auto* caches = host->cacheProvider.getCacheForMuidAsResponder (output->getIncomingHeader().source);
if (caches == nullptr)
return false;
const auto source = output->getIncomingHeader().source;
const auto dest = output->getIncomingHeader().destination;
const auto group = output->getIncomingGroup();
const auto request = data.requestID;
caches->primeCache (host->delegate.getNumSimultaneousRequestsSupported(), [this, source, dest, group, request] (const PropertyExchangeResult& result)
{
const auto send = [&] (const PropertyReplyHeader& header)
{
detail::MessageTypeUtils::send (host->output,
group,
Message::Header { ChannelInGroup::wholeBlock,
detail::MessageMeta::Meta<Message::PropertySetDataResponse>::subID2,
detail::MessageMeta::implementationVersion,
dest,
source },
Message::PropertySetDataResponse { { request, Encodings::jsonTo7BitText (header.toVarCondensed()) } });
};
if (result.getError() == PropertyExchangeResult::Error::tooManyTransactions)
{
PropertyReplyHeader header;
header.status = 343;
header.message = TRANS ("The device has initiated too many simultaneous requests");
send (header);
return;
}
if (result.getError().has_value())
{
PropertyReplyHeader header;
header.status = 400;
header.message = TRANS ("Request was incomplete");
send (header);
return;
}
send (host->delegate.propertySetDataRequested (source, { result.getHeaderAsRequestHeader(), result.getBody() }));
}, request);
caches->addChunk (data.requestID, data);
return true;
}
bool messageReceived (const Message::PropertySubscribe& data) const
{
auto* caches = host->cacheProvider.getCacheForMuidAsResponder (output->getIncomingHeader().source);
if (caches == nullptr)
return false;
if (data.header.empty() || data.thisChunkNum != 1 || data.totalNumChunks != 1)
return false;
const auto subHeader = PropertySubscriptionHeader::parseCondensed (Encodings::jsonFrom7BitText (data.header));
const auto tryNotifyInitiator = subHeader.command == PropertySubscriptionCommand::start
|| subHeader.command == PropertySubscriptionCommand::end;
if (! tryNotifyInitiator)
return false;
const auto source = output->getIncomingHeader().source;
const auto sendResponse = [&] (const PropertyReplyHeader& header)
{
detail::PropertyHostUtils::send (*output,
output->getIncomingGroup(),
detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
source,
data.requestID,
Encodings::jsonTo7BitText (header.toVarCondensed()),
{},
host->cacheProvider.getMaxSysexSizeForMuid (source));
};
if (subHeader.command == PropertySubscriptionCommand::start)
{
if (host->delegate.subscriptionStartRequested (source, subHeader))
{
auto& currentSubscribeIds = host->registry[source];
const auto newToken = findUnusedSubscribeId (currentSubscribeIds);
[[maybe_unused]] const auto pair = currentSubscribeIds.emplace (newToken, subHeader.resource);
jassert (pair.second);
const auto subscribeId = subscribeIdFromUid (newToken);
host->delegate.subscriptionDidStart (source, subscribeId, subHeader);
PropertyReplyHeader header;
header.extended["subscribeId"] = subscribeId;
sendResponse (header);
}
else
{
PropertyReplyHeader header;
header.status = 405;
sendResponse (header);
}
return true;
}
if (subHeader.command == PropertySubscriptionCommand::end)
{
const auto token = uidFromSubscribeId (subHeader.subscribeId);
auto& currentSubscribeIds = host->registry[source];
const auto iter = currentSubscribeIds.find (token);
if (iter != currentSubscribeIds.end())
{
host->delegate.subscriptionWillEnd (source, { subHeader.subscribeId, iter->second });
currentSubscribeIds.erase (iter);
sendResponse ({});
return true;
}
return false;
}
return false;
}
bool messageReceived (const Message::PropertyNotify& n) const
{
const auto m = output->getIncomingHeader().source;
if (auto* it = host->cacheProvider.getCacheForMuidAsResponder (m))
it->notify (n.requestID, n.header);
if (auto* it = host->cacheProvider.getCacheForMuidAsInitiator (m))
it->notify (n.requestID, n.header);
return true;
}
PropertyHost* host = nullptr;
ResponderOutput* output = nullptr;
bool* handled = nullptr;
};
//==============================================================================
std::set<Subscription> PropertyHost::findSubscriptionsForDevice (MUID device) const
{
const auto iter = registry.find (device);
if (iter == registry.end())
return {};
std::set<Subscription> result;
for (const auto& [subId, resource] : iter->second)
{
[[maybe_unused]] const auto pair = result.insert ({ subscribeIdFromUid (subId), resource });
jassert (pair.second);
}
return result;
}
int PropertyHost::countOngoingTransactions() const
{
const auto muids = cacheProvider.getDiscoveredMuids();
return std::accumulate (muids.begin(), muids.end(), 0, [&] (auto acc, const auto& m)
{
if (auto* cache = cacheProvider.getCacheForMuidAsResponder (m))
return acc + cache->countOngoingTransactions();
return acc;
});
}
bool PropertyHost::tryRespond (ResponderOutput& responderOutput, const Message::Parsed& message)
{
bool result = false;
detail::MessageTypeUtils::visit (message, Visitor { this, &responderOutput, &result });
return result;
}
ErasedScopeGuard PropertyHost::sendSubscriptionUpdate (MUID device,
const PropertySubscriptionHeader& header,
Span<const std::byte> body,
std::function<void (const PropertyExchangeResult&)> cb)
{
const auto deviceIter = registry.find (device);
if (deviceIter == registry.end())
{
// That device doesn't have any active subscriptions
jassertfalse;
return {};
}
const auto uid = uidFromSubscribeId (header.subscribeId);
const auto subIter = deviceIter->second.find (uid);
if (subIter == deviceIter->second.end())
{
// That subscribeId isn't currently in use by that device
jassertfalse;
return {};
}
const auto resource = subIter->second;
if (header.resource != resource)
{
// That subscribeId corresponds to a different resource
jassertfalse;
return {};
}
if (header.command == PropertySubscriptionCommand::start)
{
// This function is intended to update ongoing subscriptions. To start a new subscription,
// use CIDevice.
jassertfalse;
return {};
}
auto* caches = cacheProvider.getCacheForMuidAsInitiator (device);
if (caches == nullptr)
return {};
const auto terminator = detail::PropertyHostUtils::getTerminator (output, functionBlock, device);
auto wrappedCallback = [&]() -> std::function<void (const PropertyExchangeResult&)>
{
if (header.command != PropertySubscriptionCommand::end)
return cb;
return [this, device, uid, resource, cb] (const PropertyExchangeResult& result)
{
if (! result.getError().has_value())
{
delegate.subscriptionWillEnd (device, { subscribeIdFromUid (uid), resource });
registry[device].erase (uid);
}
NullCheckedInvocation::invoke (cb, result);
};
}();
const auto encoded = Encodings::tryEncode (body, header.mutualEncoding);
if (! encoded.has_value())
{
NullCheckedInvocation::invoke (wrappedCallback, PropertyExchangeResult { PropertyExchangeResult::Error::invalidPayload });
return {};
}
auto primed = caches->primeCache (delegate.getNumSimultaneousRequestsSupported(),
std::move (wrappedCallback),
std::move (terminator));
if (! primed.isValid())
return {};
detail::PropertyHostUtils::send (output,
functionBlock.firstGroup,
detail::MessageMeta::Meta<Message::PropertySubscribe>::subID2,
device,
primed.id,
Encodings::jsonTo7BitText (header.toVarCondensed()),
*encoded,
cacheProvider.getMaxSysexSizeForMuid (device));
return std::move (primed.token);
}
void PropertyHost::terminateSubscription (MUID device, const String& subscribeId)
{
const auto deviceIter = registry.find (device);
if (deviceIter == registry.end())
{
// That device doesn't have any active subscriptions
jassertfalse;
return;
}
const auto uid = uidFromSubscribeId (subscribeId);
const auto subIter = deviceIter->second.find (uid);
if (subIter == deviceIter->second.end())
{
// That subscribeId isn't currently in use by that device
jassertfalse;
return;
}
PropertySubscriptionHeader header;
header.command = PropertySubscriptionCommand::end;
header.subscribeId = subscribeId;
header.resource = subIter->second;
sendSubscriptionUpdate (device, header, {}, nullptr).release();
}
PropertyHost::SubscriptionToken PropertyHost::uidFromSubscribeId (String id)
{
try
{
// from_chars would be better once we no longer need to support older macOS
return { (size_t) std::stoull (id.toStdString(), {}, 36) };
}
catch (...) {}
jassertfalse;
return {};
}
String PropertyHost::subscribeIdFromUid (SubscriptionToken uid)
{
const auto str = std::to_string (uid.uid);
jassert (str.size() <= 8);
return str;
}
PropertyHost::SubscriptionToken PropertyHost::findUnusedSubscribeId (const std::map<SubscriptionToken, String>& used)
{
return ! used.empty() ? SubscriptionToken { std::prev (used.end())->first.uid + 1 } : SubscriptionToken { 0 };
}
} // namespace juce::midi_ci

View file

@ -0,0 +1,125 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Acting as a ResponderListener, instances of this class can formulate
appropriate replies to property transactions initiated by remote devices.
PropertyHost instances also contain methods to inform remote devices about
changes to local property state.
Keeps track of property subscriptions requested by remote devices.
@tags{Audio}
*/
class PropertyHost final : public ResponderDelegate
{
public:
/** @internal
Rather than constructing one of these objects yourself, you should configure
a Device with property exchange support, and then use Device::getPropertyHost()
to retrieve a property host that has been set up to work with that device.
*/
PropertyHost (FunctionBlock fb, PropertyDelegate& d, BufferOutput& o, CacheProvider& p)
: functionBlock (fb), delegate (d), output (o), cacheProvider (p) {}
/** Sends a "Subscription" message from a device, when acting as a
subscription responder. You should call this for all registered
subscribers whenever the subscribed property is modified in a way that
remote devices don't know about (if a remote device requests a
property update, there's no need to send a subscription update after
changing the property accordingly).
You should *not* attempt to start a new subscription on another device
using this function. Valid subscription commands are "full", "partial",
and "notify". Check the property exchange specification for the intended
use of these commands.
To terminate a subscription that was initiated by a remote device,
use terminateSubscription().
The provided callback will be called once the remote device has confirmed
receipt of the subscription update. If the state of your application
changes such that you no longer need to respond/wait for confirmation,
you can allow the returned Guard to fall out of scope, or reset it
manually.
*/
ErasedScopeGuard sendSubscriptionUpdate (MUID device,
const PropertySubscriptionHeader& header,
Span<const std::byte> body,
std::function<void (const PropertyExchangeResult&)> callback);
/** Terminates a subscription that was started by a remote device.
This may be useful if your application has properties that can be
added and removed - you can terminate subscriptions to subscribed
properties before removing those properties.
*/
void terminateSubscription (MUID device, const String& subscribeId);
/** Returns a set of subscribed resources.
This set contains all active subscriptionIDs for the given device,
along with the resources to which those subscriptionIDs refer.
*/
std::set<Subscription> findSubscriptionsForDevice (MUID device) const;
/** Returns the number of transactions that have been initiated by other devices, but not yet
completed, normally because the request has been split into several messages.
*/
int countOngoingTransactions() const;
/** @internal */
bool tryRespond (ResponderOutput&, const Message::Parsed&) override;
private:
class Visitor;
struct SubscriptionToken
{
size_t uid{};
bool operator< (const SubscriptionToken& other) const { return uid < other.uid; }
bool operator== (const SubscriptionToken& other) const { return uid == other.uid; }
bool operator!= (const SubscriptionToken& other) const { return uid != other.uid; }
};
static SubscriptionToken uidFromSubscribeId (String id);
static String subscribeIdFromUid (SubscriptionToken uid);
static SubscriptionToken findUnusedSubscribeId (const std::map<SubscriptionToken, String>& used);
FunctionBlock functionBlock;
PropertyDelegate& delegate;
BufferOutput& output;
CacheProvider& cacheProvider;
std::map<MUID, std::map<SubscriptionToken, String>> registry;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,50 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
An interface for types that implement responses for certain message types.
@tags{Audio}
*/
class ResponderDelegate
{
public:
ResponderDelegate() = default;
virtual ~ResponderDelegate() = default;
/** If the message is processed successfully, and a response sent, then
this returns true. Otherwise, returns false, allowing other ResponderDelegates
to attempt to handle the message if necessary.
*/
virtual bool tryRespond (ResponderOutput& output, const Message::Parsed& message) = 0;
JUCE_DECLARE_NON_COPYABLE (ResponderDelegate)
JUCE_DECLARE_NON_MOVEABLE (ResponderDelegate)
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,44 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
ChannelAddress ResponderOutput::getChannelAddress() const
{
return ChannelAddress{}.withGroup (getIncomingGroup())
.withChannel (getIncomingHeader().deviceID);
}
Message::Header ResponderOutput::getReplyHeader (std::byte replySubID) const
{
return { getIncomingHeader().deviceID,
replySubID,
detail::MessageMeta::implementationVersion,
getMuid(),
getIncomingHeader().source };
}
} // namespace juce::midi_ci

View file

@ -0,0 +1,83 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Represents a destination into which MIDI-CI messages can be written.
Each message should be written into the output buffer. Then, send() will
send the current contents of the buffer to the specified group.
@tags{Audio}
*/
class BufferOutput
{
public:
BufferOutput() = default;
virtual ~BufferOutput() = default;
/** Returns the MUID of the responder. */
virtual MUID getMuid() const = 0;
/** Returns the buffer into which replies should be written. */
virtual std::vector<std::byte>& getOutputBuffer() = 0;
/** Sends the current contents of the buffer to the provided group. */
virtual void send (uint8_t group) = 0;
JUCE_DECLARE_NON_COPYABLE (BufferOutput)
JUCE_DECLARE_NON_MOVEABLE (BufferOutput)
};
//==============================================================================
/**
A buffer output that additionally provides information about an incoming message, so that
an appropriate reply can be constructed for that message.
@tags{Audio}
*/
class ResponderOutput : public BufferOutput
{
public:
/** Returns the header of the message that was received. */
virtual Message::Header getIncomingHeader() const = 0;
/** Returns the group of the message that was received. */
virtual uint8_t getIncomingGroup() const = 0;
/** Returns the channel to which the incoming message was addressed. */
ChannelAddress getChannelAddress() const;
/** Returns a default header that can be used for outgoing replies.
This always sets the destination MUID equal to the source MUID of the incoming header,
so it's not suitable for broadcast messages.
*/
Message::Header getReplyHeader (std::byte replySubID) const;
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,54 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
//==============================================================================
/**
Matches a subscription ID to a resource name.
@tags{Audio}
*/
struct Subscription
{
String subscribeId;
String resource;
bool operator< (const Subscription& other) const { return subscribeId < other.subscribeId; }
bool operator<= (const Subscription& other) const { return subscribeId <= other.subscribeId; }
bool operator> (const Subscription& other) const { return subscribeId > other.subscribeId; }
bool operator>= (const Subscription& other) const { return subscribeId >= other.subscribeId; }
bool operator== (const Subscription& other) const
{
const auto tie = [] (const auto& x) { return std::tie (x.subscribeId, x.resource); };
return tie (*this) == tie (other);
}
bool operator!= (const Subscription& other) const { return ! operator== (other); }
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,55 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci
{
/**
Holds the maximum number of channels that may be activated for a MIDI-CI
profile, along with the number of channels that are currently active.
@tags{Audio}
*/
struct SupportedAndActive
{
uint16_t supported{}; ///< The maximum number of member channels for a profile. 0 indicates that the profile is unsupported.
uint16_t active{}; ///< The number of member channels currently active for a profile. 0 indicates that the profile is inactive.
/** Returns true if supported is non-zero. */
bool isSupported() const { return supported != 0; }
/** Returns true if active is non-zero. */
bool isActive() const { return active != 0; }
bool operator== (const SupportedAndActive& other) const
{
const auto tie = [] (auto& x) { return std::tie (x.supported, x.active); };
return tie (*this) == tie (other);
}
bool operator!= (const SupportedAndActive& other) const { return ! operator== (other); }
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,376 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*
Utilities for converting sequences of bytes to and from
C++ struct types.
*/
namespace juce::midi_ci::detail::Marshalling
{
template <uint8_t> struct IntForNumBytes;
template <> struct IntForNumBytes<1> { using Type = uint8_t; };
template <> struct IntForNumBytes<2> { using Type = uint16_t; };
template <> struct IntForNumBytes<4> { using Type = uint32_t; };
template <uint8_t NumBytes> using IntForNumBytesT = typename IntForNumBytes<NumBytes>::Type;
//==============================================================================
/*
Reads a sequence of bytes representing a MIDI-CI message, and populates
structs with the information contained in the message.
*/
class Reader
{
public:
/* Constructs a reader that will parse the provided buffer, using the most
recent known MIDI-CI version.
*/
explicit Reader (Span<const std::byte> b)
: Reader (b, static_cast<uint8_t> (MessageMeta::implementationVersion)) {}
/* Constructs a reader for the provided MIDI-CI version that will parse
the provided buffer. Fields introduced in later versions will be ignored,
and so left with their default values.
*/
Reader (Span<const std::byte> b, int v)
: bytes (b), version (v) {}
std::optional<int> getVersion() const { return version; }
/* Attempts to interpret the byte sequence passed to the constructor
as a sequence of structs 'T'.
Returns true if parsing succeeds, otherwise returns false.
*/
template <typename... T>
bool operator() (T&&... t)
{
return (doArchiveChecked (std::forward<T> (t)) && ...);
}
private:
template <typename T>
bool doArchiveChecked (T&& t)
{
if (failed)
return false;
doArchive (t);
return ! failed;
}
void doArchive (ChannelInGroup& x)
{
if (const auto popped = popBytes (1))
{
const auto p = *popped;
x = ChannelInGroup (p[0] & std::byte { 0x7f });
return;
}
failed = true;
}
// If we're trying to parse into a constant, then we should check that the next byte(s)
// match that constant.
void doArchive (const std::byte& x)
{
std::byte temp{};
if (! doArchiveChecked (temp))
return;
failed |= x != temp;
}
void doArchive (std::byte& x)
{
if (const auto popped = popBytes (1))
{
const auto p = *popped;
x = p[0] & std::byte { 0x7f };
return;
}
failed = true;
}
void doArchive (const uint16_t& x)
{
uint16_t temp{};
if (! doArchiveChecked (temp))
return;
failed |= temp != x;
}
void doArchive (uint16_t& x)
{
if (const auto popped = popBytes (2))
{
const auto p = *popped;
x = (uint16_t) (((uint16_t) p[0] & 0x7f) << 0x00)
| (uint16_t) (((uint16_t) p[1] & 0x7f) << 0x07);
return;
}
failed = true;
}
void doArchive (const uint32_t& x)
{
uint32_t temp{};
if (! doArchiveChecked (temp))
return;
failed |= temp != x;
}
void doArchive (uint32_t& x)
{
if (const auto popped = popBytes (4))
{
const auto p = *popped;
x = (((uint32_t) p[0] & 0x7f) << 0x00)
| (((uint32_t) p[1] & 0x7f) << 0x07)
| (((uint32_t) p[2] & 0x7f) << 0x0e)
| (((uint32_t) p[3] & 0x7f) << 0x15);
return;
}
failed = true;
}
template <uint8_t NumBytes, bool B>
void doArchive (MessageMeta::SpanWithSizeBytes<NumBytes, Span<const std::byte>, B> x)
{
IntForNumBytesT<NumBytes> numBytes{};
// Read the number of bytes in the field
if (! doArchiveChecked (numBytes))
return;
// Attempt to pop that many bytes
if (const auto popped = popBytes (numBytes))
{
x.span = *popped;
return;
}
failed = true;
}
template <uint8_t NumBytes, size_t N>
void doArchive (MessageMeta::SpanWithSizeBytes<NumBytes, Span<const std::array<std::byte, N>>> x)
{
IntForNumBytesT<NumBytes> numItems{};
// Read the number of items in the field
if (! doArchiveChecked (numItems))
return;
if (const auto popped = popBytes (numItems * N))
{
x.span = Span (unalignedPointerCast<const std::array<std::byte, N>*> (popped->data()), numItems);
return;
}
failed = true;
}
template <size_t N>
void doArchive (Span<const std::byte, N>& x)
{
if (const auto popped = popBytes (bytes.size()))
{
x = *popped;
return;
}
failed = true;
}
template <size_t N>
void doArchive (std::array<std::byte, N>& x)
{
if (const auto popped = popBytes (x.size()))
{
const auto p = *popped;
std::transform (p.begin(), p.end(), x.begin(), [] (std::byte b)
{
return b & std::byte { 0x7f };
});
return;
}
failed = true;
}
template <typename T>
void doArchive (T& t)
{
juce::detail::doLoad (*this, t);
}
template <typename T>
void doArchive (Named<T> named)
{
doArchiveChecked (named.value);
}
std::optional<Span<const std::byte>> popBytes (size_t num)
{
if (bytes.size() < num)
return {};
const Span result { bytes.data(), num };
bytes = Span { bytes.data() + num, bytes.size() - num };
return result;
}
Span<const std::byte> bytes; /* Bytes making up a CI message. */
int version{}; /* The version to assume when parsing the message, specified in the message header. */
bool failed = false;
};
//==============================================================================
/*
Converts one or more structs into a byte sequence suitable for transmission
as a MIDI-CI message.
*/
class Writer
{
public:
/* Constructs a writer that will write into the provided buffer. */
explicit Writer (std::vector<std::byte>& b)
: Writer (b, static_cast<uint8_t> (MessageMeta::implementationVersion)) {}
/* Constructs a writer that will write a MIDI-CI message of the requested
version to the provided buffer.
Fields introduced in later MIDI-CI versions will be ignored.
*/
Writer (std::vector<std::byte>& b, int v)
: bytes (b), version (v) {}
std::optional<int> getVersion() const { return version; }
/* Formats the information contained in the provided structs into a
MIDI-CI message, and returns a bool indicating success or failure.
*/
template <typename... T>
bool operator() (const T&... t)
{
return (doArchiveChecked (t) && ...);
}
private:
template <typename T>
bool doArchiveChecked (T&& t)
{
if (failed)
return false;
doArchive (t);
return ! failed;
}
void doArchive (ChannelInGroup x)
{
doArchiveChecked (std::byte (x));
}
void doArchive (std::byte x)
{
bytes.push_back (x);
}
void doArchive (uint16_t x)
{
bytes.insert (bytes.end(), { (std::byte) ((x >> 0x00) & 0x7f),
(std::byte) ((x >> 0x07) & 0x7f) });
}
void doArchive (uint32_t x)
{
bytes.insert (bytes.end(), { (std::byte) ((x >> 0x00) & 0x7f),
(std::byte) ((x >> 0x07) & 0x7f),
(std::byte) ((x >> 0x0e) & 0x7f),
(std::byte) ((x >> 0x15) & 0x7f) });
}
template <uint8_t NumBytes, typename T, bool B>
void doArchive (MessageMeta::SpanWithSizeBytes<NumBytes, T, B> x)
{
if (x.span.size() >= (1 << (7 * NumBytes)))
{
// Unable to express the size of the field in the requested number of bytes
jassertfalse;
failed = true;
return;
}
// Write the number of bytes, followed by the bytes themselves.
const auto numBytes = (IntForNumBytesT<NumBytes>) x.span.size();
doArchiveChecked (numBytes);
doArchiveChecked (x.span);
}
template <typename T, size_t N>
void doArchive (Span<const T, N> x)
{
failed = ! std::all_of (x.begin(), x.end(), [&] (const auto& item)
{
return doArchiveChecked (item);
});
}
template <size_t N>
void doArchive (const std::array<std::byte, N>& x)
{
bytes.insert (bytes.end(), x.begin(), x.end());
}
template <typename T>
void doArchive (const T& t)
{
juce::detail::doSave (*this, t);
}
template <typename T>
void doArchive (Named<T> named)
{
doArchiveChecked (named.value);
}
std::vector<std::byte>& bytes; /* The buffer that will hold the completed message. */
int version{}; /* The version to assume when writing the message, specified in the message header. */
bool failed = false;
};
} // namespace juce::midi_ci::detail::Marshalling

View file

@ -0,0 +1,624 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*
Namespace containing metadata about MIDI-CI message types, such as
replies corresponding to inquiries, and serialization functions.
@tags{Audio}
*/
namespace juce::midi_ci::detail::MessageMeta
{
//==============================================================================
/* The maximum CI version that can be parsed and generated by this implementation. */
static constexpr std::byte implementationVersion { 0x02 };
/* @internal
Wraps a pointer to a Span. Used to indicate to CI readers/writers that a particular field is
of variable length, starting with a 16-bit or 32-bit byte count.
*/
template <uint8_t NumBytes, typename T, bool isJson = false>
struct SpanWithSizeBytes
{
T& span;
};
/* @internal
Creates a SpanWithSizeBytes with an appropriate template argument.
*/
template <uint8_t NumBytes, typename T>
static constexpr auto makeSpanWithSizeBytes ( Span<T>& span) { return SpanWithSizeBytes<NumBytes, Span<T>> { span }; }
template <uint8_t NumBytes, typename T>
static constexpr auto makeSpanWithSizeBytes (const Span<T>& span) { return SpanWithSizeBytes<NumBytes, const Span<T>> { span }; }
template <uint8_t NumBytes, typename T>
static constexpr auto makeJsonWithSizeBytes ( Span<T>& span) { return SpanWithSizeBytes<NumBytes, Span<T>, true> { span }; }
template <uint8_t NumBytes, typename T>
static constexpr auto makeJsonWithSizeBytes (const Span<T>& span) { return SpanWithSizeBytes<NumBytes, const Span<T>, true> { span }; }
template <uint8_t SubID2, typename R = void>
struct Metadata
{
static constexpr std::byte subID2 { SubID2 };
using Reply = R;
};
template <typename T>
struct Meta;
template <>
struct Meta<Message::DiscoveryResponse> : Metadata<0x71> {};
template <>
struct Meta<Message::Discovery> : Metadata<0x70, Message::DiscoveryResponse> {};
template <>
struct Meta<Message::EndpointInquiryResponse> : Metadata<0x73> {};
template <>
struct Meta<Message::EndpointInquiry> : Metadata<0x72, Message::EndpointInquiryResponse> {};
template <>
struct Meta<Message::InvalidateMUID> : Metadata<0x7e> {};
template <>
struct Meta<Message::ACK> : Metadata<0x7d> {};
template <>
struct Meta<Message::NAK> : Metadata<0x7f> {};
template <>
struct Meta<Message::ProfileInquiryResponse> : Metadata<0x21> {};
template <>
struct Meta<Message::ProfileInquiry> : Metadata<0x20, Message::ProfileInquiryResponse> {};
template <>
struct Meta<Message::ProfileAdded> : Metadata<0x26> {};
template <>
struct Meta<Message::ProfileRemoved> : Metadata<0x27> {};
template <>
struct Meta<Message::ProfileDetailsResponse> : Metadata<0x29> {};
template <>
struct Meta<Message::ProfileDetails> : Metadata<0x28, Message::ProfileDetailsResponse> {};
template <>
struct Meta<Message::ProfileOn> : Metadata<0x22> {};
template <>
struct Meta<Message::ProfileOff> : Metadata<0x23> {};
template <>
struct Meta<Message::ProfileEnabledReport> : Metadata<0x24> {};
template <>
struct Meta<Message::ProfileDisabledReport> : Metadata<0x25> {};
template <>
struct Meta<Message::ProfileSpecificData> : Metadata<0x2f> {};
template <>
struct Meta<Message::PropertyExchangeCapabilitiesResponse> : Metadata<0x31> {};
template <>
struct Meta<Message::PropertyExchangeCapabilities> : Metadata<0x30, Message::PropertyExchangeCapabilitiesResponse> {};
template <>
struct Meta<Message::StaticSizePropertyExchange> {};
template <>
struct Meta<Message::DynamicSizePropertyExchange> {};
template <>
struct Meta<Message::PropertyGetDataResponse> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x35> {};
template <>
struct Meta<Message::PropertyGetData> : Meta<Message::StaticSizePropertyExchange>, Metadata<0x34, Message::PropertyGetDataResponse> {};
template <>
struct Meta<Message::PropertySetDataResponse> : Meta<Message::StaticSizePropertyExchange>, Metadata<0x37> {};
template <>
struct Meta<Message::PropertySetData> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x36, Message::PropertySetDataResponse> {};
template <>
struct Meta<Message::PropertySubscribeResponse> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x39> {};
template <>
struct Meta<Message::PropertySubscribe> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x38, Message::PropertySubscribeResponse> {};
template <>
struct Meta<Message::PropertyNotify> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x3f> {};
template <>
struct Meta<Message::ProcessInquiryResponse> : Metadata<0x41> {};
template <>
struct Meta<Message::ProcessInquiry> : Metadata<0x40, Message::ProcessInquiryResponse> {};
template <>
struct Meta<Message::ProcessMidiMessageReportResponse> : Metadata<0x43> {};
template <>
struct Meta<Message::ProcessMidiMessageReport> : Metadata<0x42, Message::ProcessMidiMessageReportResponse> {};
template <>
struct Meta<Message::ProcessEndMidiMessageReport> : Metadata<0x44> {};
} // namespace juce::midi_ci::detail::MessageMeta
namespace juce
{
struct VersionBase
{
static constexpr auto marshallingVersion = (int) juce::midi_ci::detail::MessageMeta::implementationVersion;
};
template <uint8_t NumBytes, typename T, bool isJson>
struct SerialisationTraits<midi_ci::detail::MessageMeta::SpanWithSizeBytes<NumBytes, T, isJson>>
{
static constexpr auto marshallingVersion = std::nullopt;
template <typename This>
static auto getSize (This& t)
{
if constexpr (NumBytes == 1)
return (uint8_t) t.size();
else if constexpr (NumBytes == 2)
return (uint16_t) t.size();
else if constexpr (NumBytes == 4)
return (uint32_t) t.size();
else if constexpr (NumBytes == 8)
return (uint64_t) t.size();
else
static_assert (detail::delayStaticAssert<T>, "NumBytes is not a power of two");
}
template <typename Archive, typename This>
static auto load (Archive&, This&)
{
}
template <typename Archive, typename This>
static auto save (Archive& archive, const This& t)
{
auto size = getSize (t.span);
archive (serialisationSize (size));
for (const auto& element : t.span)
archive (element);
}
};
template <>
struct SerialisationTraits<ci::MUID> : VersionBase
{
template <typename Archive>
static auto load (Archive& archive, ci::MUID& t)
{
uint32_t muid{};
auto result = archive (muid);
t = ci::MUID::makeUnchecked (muid);
return result;
}
template <typename Archive>
static auto save (Archive& archive, const ci::MUID& t)
{
return archive (t.get());
}
};
template <>
struct SerialisationTraits<ci::Message::Header> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
const std::byte universalSystemExclusive { 0x7e }, subID { 0x0d };
return archive (universalSystemExclusive,
t.deviceID,
subID,
t.category,
t.version,
t.source,
t.destination);
}
};
template <>
struct SerialisationTraits<ci::Message::Generic> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
return archive (t.header, t.data);
}
};
template <>
struct SerialisationTraits<ci::Message::DiscoveryResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("device", t.device),
named ("capabilities", t.capabilities),
named ("maximumSysexSize", t.maximumSysexSize));
if (0x02 <= archive.getVersion())
archive (named ("outputPathID", t.outputPathID), named ("functionBlock", t.functionBlock));
}
};
template <>
struct SerialisationTraits<ci::Message::Discovery> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("device", t.device),
named ("capabilities", t.capabilities),
named ("maximumSysexSize", t.maximumSysexSize));
if (0x02 <= archive.getVersion())
archive (named ("outputPathID", t.outputPathID));
}
};
template <>
struct SerialisationTraits<ci::Message::EndpointInquiryResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("status", t.status),
named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
}
};
template <>
struct SerialisationTraits<ci::Message::EndpointInquiry> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("status", t.status));
}
};
template <>
struct SerialisationTraits<ci::Message::InvalidateMUID> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("target", t.target));
}
};
template <>
struct SerialisationTraits<ci::Message::ACK> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("originalCategory", t.originalCategory),
named ("statusCode", t.statusCode),
named ("statusData", t.statusData),
named ("details", t.details),
named ("messageText", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.messageText)));
}
};
template <>
struct SerialisationTraits<ci::Message::NAK> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
if (0x02 <= archive.getVersion())
{
archive (named ("originalCategory", t.originalCategory),
named ("statusCode", t.statusCode),
named ("statusData", t.statusData),
named ("details", t.details),
named ("messageText", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.messageText)));
}
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileInquiryResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("enabledProfiles", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.enabledProfiles)),
named ("disabledProfiles", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.disabledProfiles)));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileInquiry> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive&, This&)
{
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileAdded> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileRemoved> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileDetailsResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile),
named ("target", t.target),
named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileDetails> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile),
named ("target", t.target));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileOn> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile));
if (0x02 <= archive.getVersion())
archive (named ("numChannels", t.numChannels));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileOff> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile));
if (0x02 <= archive.getVersion())
{
uint16_t reserved{};
archive (named ("reserved", reserved));
}
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileEnabledReport> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile));
if (0x02 <= archive.getVersion())
archive (named ("numChannels", t.numChannels));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileDisabledReport> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile));
if (0x02 <= archive.getVersion())
archive (named ("numChannels", t.numChannels));
}
};
template <>
struct SerialisationTraits<ci::Message::ProfileSpecificData> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("profile", t.profile), named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<4> (t.data)));
}
};
template <>
struct SerialisationTraits<ci::Message::PropertyExchangeCapabilitiesResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("numRequests", t.numSimultaneousRequestsSupported));
if (0x02 <= archive.getVersion())
archive (named ("major", t.majorVersion), named ("minor", t.minorVersion));
}
};
template <>
struct SerialisationTraits<ci::Message::PropertyExchangeCapabilities> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("numRequests", t.numSimultaneousRequestsSupported));
if (0x02 <= archive.getVersion())
archive (named ("major", t.majorVersion), named ("minor", t.minorVersion));
}
};
template <>
struct SerialisationTraits<ci::Message::StaticSizePropertyExchange> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
const uint16_t chunkNum = 1, dataLength = 0;
archive (named ("requestID", t.requestID),
named ("header", midi_ci::detail::MessageMeta::makeJsonWithSizeBytes<2> (t.header)),
named ("numChunks", chunkNum),
named ("thisChunk", chunkNum),
named ("length", dataLength));
}
};
template <>
struct SerialisationTraits<ci::Message::DynamicSizePropertyExchange> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (named ("requestID", t.requestID),
named ("header", midi_ci::detail::MessageMeta::makeJsonWithSizeBytes<2> (t.header)),
named ("numChunks", t.totalNumChunks),
named ("thisChunk", t.thisChunkNum),
named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
}
};
template <>
struct SerialisationTraits<ci::Message::PropertyGetDataResponse> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::PropertyGetData> : SerialisationTraits<ci::Message::StaticSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::PropertySetDataResponse> : SerialisationTraits<ci::Message::StaticSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::PropertySetData> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::PropertySubscribeResponse> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::PropertySubscribe> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::PropertyNotify> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
template <>
struct SerialisationTraits<ci::Message::ProcessInquiryResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
archive (t.supportedFeatures);
}
};
template <>
struct SerialisationTraits<ci::Message::ProcessInquiry> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive&, This&)
{
}
};
template <>
struct SerialisationTraits<ci::Message::ProcessMidiMessageReportResponse> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
std::byte reserved{};
archive (named ("messageDataControl", t.messageDataControl),
named ("requestedMessages", t.requestedMessages),
named ("reserved", reserved),
named ("channelControllerMessages", t.channelControllerMessages),
named ("noteDataMessages", t.noteDataMessages));
}
};
template <>
struct SerialisationTraits<ci::Message::ProcessMidiMessageReport> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive& archive, This& t)
{
std::byte reserved{};
archive (named ("messageDataControl", t.messageDataControl),
named ("requestedMessages", t.requestedMessages),
named ("reserved", reserved),
named ("channelControllerMessages", t.channelControllerMessages),
named ("noteDataMessages", t.noteDataMessages));
}
};
template <>
struct SerialisationTraits<ci::Message::ProcessEndMidiMessageReport> : VersionBase
{
template <typename Archive, typename This>
static auto serialise (Archive&, This&)
{
}
};
} // namespace juce

View file

@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci::detail::MessageTypeUtils
{
//==============================================================================
/*
An interface used for types that want to operate on parsed MIDI-CI messages.
@tags{Audio}
*/
struct MessageVisitor
{
MessageVisitor() = default;
MessageVisitor (const MessageVisitor&) = default;
MessageVisitor (MessageVisitor&&) = default;
MessageVisitor& operator= (const MessageVisitor&) = default;
MessageVisitor& operator= (MessageVisitor&&) = default;
virtual ~MessageVisitor() = default;
virtual void visit (const std::monostate&) const {}
virtual void visit (const Message::Discovery&) const {}
virtual void visit (const Message::EndpointInquiry&) const {}
virtual void visit (const Message::ProfileInquiry&) const {}
virtual void visit (const Message::ProfileDetails&) const {}
virtual void visit (const Message::PropertyExchangeCapabilities&) const {}
virtual void visit (const Message::PropertyGetData&) const {}
virtual void visit (const Message::PropertySetData&) const {}
virtual void visit (const Message::PropertySubscribe&) const {}
virtual void visit (const Message::ProcessInquiry&) const {}
virtual void visit (const Message::ProcessMidiMessageReport&) const {}
virtual void visit (const Message::DiscoveryResponse&) const {}
virtual void visit (const Message::EndpointInquiryResponse&) const {}
virtual void visit (const Message::InvalidateMUID&) const {}
virtual void visit (const Message::ACK&) const {}
virtual void visit (const Message::NAK&) const {}
virtual void visit (const Message::ProfileInquiryResponse&) const {}
virtual void visit (const Message::ProfileAdded&) const {}
virtual void visit (const Message::ProfileRemoved&) const {}
virtual void visit (const Message::ProfileDetailsResponse&) const {}
virtual void visit (const Message::ProfileOn&) const {}
virtual void visit (const Message::ProfileOff&) const {}
virtual void visit (const Message::ProfileEnabledReport&) const {}
virtual void visit (const Message::ProfileDisabledReport&) const {}
virtual void visit (const Message::ProfileSpecificData&) const {}
virtual void visit (const Message::PropertyExchangeCapabilitiesResponse&) const {}
virtual void visit (const Message::PropertyGetDataResponse&) const {}
virtual void visit (const Message::PropertySetDataResponse&) const {}
virtual void visit (const Message::PropertySubscribeResponse&) const {}
virtual void visit (const Message::PropertyNotify&) const {}
virtual void visit (const Message::ProcessInquiryResponse&) const {}
virtual void visit (const Message::ProcessMidiMessageReportResponse&) const {}
virtual void visit (const Message::ProcessEndMidiMessageReport&) const {}
};
using ParseFn = Message::Parsed::Body (*) (Message::Generic, Parser::Status* status);
using VisitFn = void (*) (const Message::Parsed&, const MessageVisitor&);
/* These return the Universal System Exclusive Sub-ID#2 for a particular message type. */
template <typename Specific>
static constexpr auto getParserFor (std::in_place_type_t<Specific>)
{
return [] (Message::Generic message, Parser::Status* status) -> Message::Parsed::Body
{
// Parse messages using the version specified in the header of the message
if (Specific parsed; Marshalling::Reader { message.data, static_cast<uint8_t> (message.header.version) } (parsed))
{
return parsed;
}
if (status != nullptr)
*status = Parser::Status::malformed;
return std::monostate{};
};
}
template <typename Specific>
static constexpr auto getVisitorFor (std::in_place_type_t<Specific>)
{
return [] (const Message::Parsed& parsed, const MessageVisitor& visitor)
{
if (auto* body = std::get_if<Specific> (&parsed.body))
visitor.visit (*body);
};
}
template <typename... Ts>
struct LookupTables
{
constexpr LookupTables()
{
for (auto& x : parsers)
{
x = [] (Message::Generic, Parser::Status* status) -> Message::Parsed::Body
{
if (status != nullptr)
*status = Parser::Status::unrecognisedMessage;
return std::monostate{};
};
}
for (auto& x : visitors)
{
x = [] (const Message::Parsed&, const MessageVisitor& visitor)
{
visitor.visit (std::monostate{});
};
}
(registerTag (std::in_place_type<Ts>), ...);
}
template <typename T>
constexpr void registerTag (std::in_place_type_t<T> tag)
{
constexpr auto category = MessageMeta::Meta<T>::subID2;
parsers[uint8_t (category)] = getParserFor (tag);
visitors[uint8_t (category)] = getVisitorFor (tag);
}
ParseFn parsers[std::numeric_limits<uint8_t>::max()]{};
VisitFn visitors[std::numeric_limits<uint8_t>::max()]{};
};
template <typename Body>
static void send (BufferOutput& output, uint8_t group, const Message::Header& header, const Body& body)
{
output.getOutputBuffer().clear();
Marshalling::Writer { output.getOutputBuffer() } (header, body);
output.send (group);
}
template <typename Body>
static void send (BufferOutput& output, uint8_t group, MUID targetMuid, ChannelInGroup cig, const Body& body)
{
Message::Header header
{
cig,
MessageMeta::Meta<Body>::subID2,
MessageMeta::implementationVersion,
output.getMuid(),
targetMuid,
};
send (output, group, header, body);
}
template <typename Body>
static void send (ResponderOutput& output, const Body& body)
{
send (output, output.getIncomingGroup(), output.getReplyHeader (MessageMeta::Meta<Body>::subID2), body);
}
static void sendNAK (ResponderOutput& output, std::byte statusCode)
{
const auto header = output.getReplyHeader (MessageMeta::Meta<Message::NAK>::subID2);
const Message::NAK body { output.getIncomingHeader().category,
statusCode,
std::byte { 0x00 },
{}, // No additional details
{} }; // No message text
send (output, output.getIncomingGroup(), header, body);
}
class BaseCaseDelegate : public ResponderDelegate
{
public:
bool tryRespond (ResponderOutput& output, const Message::Parsed&) override
{
sendNAK (output, {});
return true;
}
};
static constexpr auto getTables()
{
return LookupTables<Message::Discovery,
Message::DiscoveryResponse,
Message::InvalidateMUID,
Message::EndpointInquiry,
Message::EndpointInquiryResponse,
Message::ACK,
Message::NAK,
Message::ProfileInquiry,
Message::ProfileInquiryResponse,
Message::ProfileAdded,
Message::ProfileRemoved,
Message::ProfileDetails,
Message::ProfileDetailsResponse,
Message::ProfileOn,
Message::ProfileOff,
Message::ProfileEnabledReport,
Message::ProfileDisabledReport,
Message::ProfileSpecificData,
Message::PropertyExchangeCapabilities,
Message::PropertyExchangeCapabilitiesResponse,
Message::PropertyGetData,
Message::PropertyGetDataResponse,
Message::PropertySetData,
Message::PropertySetDataResponse,
Message::PropertySubscribe,
Message::PropertySubscribeResponse,
Message::PropertyNotify,
Message::ProcessInquiry,
Message::ProcessInquiryResponse,
Message::ProcessMidiMessageReport,
Message::ProcessMidiMessageReportResponse,
Message::ProcessEndMidiMessageReport>{};
}
static void visit (const Message::Parsed& msg, const MessageVisitor& visitor)
{
constexpr auto tables = getTables();
const auto fn = tables.visitors[(uint8_t) msg.header.category];
fn (msg, visitor);
}
} // namespace juce::midi_ci::detail::MessageTypeUtils

View file

@ -0,0 +1,152 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci::detail
{
PropertyDataMessageChunker::PropertyDataMessageChunker (std::vector<std::byte>& storageIn,
int chunkSizeIn,
const std::byte messageKindIn,
const std::byte requestIdIn,
Span<const std::byte> headerIn,
MUID sourceIn,
MUID destIn,
InputStream& bodyIn)
: header (headerIn),
storage (&storageIn),
body (&bodyIn),
source (sourceIn),
dest (destIn),
chunkSize (chunkSizeIn),
messageKind (messageKindIn),
requestId (requestIdIn)
{
if (hasRoomForBody())
{
populateStorage();
}
else
{
// Header too large! There's no way to fit this message into the requested chunk size.
jassertfalse;
*this = PropertyDataMessageChunker();
}
}
PropertyDataMessageChunker& PropertyDataMessageChunker::operator++() noexcept
{
if (*this != PropertyDataMessageChunker())
{
if (body->isExhausted())
{
*this = PropertyDataMessageChunker();
}
else
{
++thisChunk;
populateStorage();
}
}
return *this;
}
Span<const std::byte> PropertyDataMessageChunker::operator*() const noexcept
{
// The end of the stream was reached, no point dereferencing the iterator now!
jassert (storage != nullptr && (int) storage->size() <= chunkSize);
return *storage;
}
Span<const std::byte> PropertyDataMessageChunker::getHeaderForBlock() const
{
return thisChunk == 1 ? header : Span<const std::byte>{};
}
int PropertyDataMessageChunker::getRoomForBody() const
{
return chunkSize - (int) (getHeaderForBlock().size() + 22);
}
bool PropertyDataMessageChunker::hasRoomForBody() const
{
const auto bodyRoom = getRoomForBody();
return (0 < bodyRoom)
|| (0 == bodyRoom && body->getNumBytesRemaining() == 0);
}
void PropertyDataMessageChunker::populateStorage() const
{
storage->clear();
storage->resize ((size_t) getRoomForBody());
// Read body data into buffer
const auto numBytesRead = (uint16_t) jmax (ssize_t (0), body->read (storage->data(), storage->size()));
const auto [numChunks, thisChunkNum] = [&]() -> std::tuple<uint16_t, uint16_t>
{
if (body->isExhausted() || body->getNumBytesRemaining() == 0)
return std::tuple (thisChunk, thisChunk);
const auto totalLength = body->getTotalLength();
if (totalLength < 0)
return std::tuple ((uint16_t) 0, thisChunk); // 0 means "unknown number"
const auto roomForBody = getRoomForBody();
if (roomForBody != 0)
return std::tuple ((uint16_t) ((totalLength + roomForBody - 1) / roomForBody), thisChunk);
// During construction, the input stream reported that it had no data remaining, so no
// space was reserved for body content.
// Now, the input stream reports that it has data remaining, but there's nowhere
// to fit it in the message!
jassertfalse;
return std::tuple (thisChunk, (uint16_t) 0); // 0 means "data potentially unusable"
}();
// Now we know how many bytes we managed to read, write the header at the end of the buffer
const auto headerForBlock = getHeaderForBlock();
detail::Marshalling::Writer writer { *storage };
writer (Message::Header { ChannelInGroup::wholeBlock,
messageKind,
detail::MessageMeta::implementationVersion,
source,
dest },
requestId,
detail::MessageMeta::makeSpanWithSizeBytes<2> (headerForBlock),
numChunks,
thisChunkNum,
numBytesRead);
// Finally, swap the header to the beginning of the buffer
std::rotate (storage->begin(), storage->begin() + getRoomForBody(), storage->end());
// ...and bring the storage buffer down to size, if we didn't manage to fill it
storage->resize (storage->size() + numBytesRead - (size_t) getRoomForBody());
}
} // namespace juce::midi_ci::detail

View file

@ -0,0 +1,97 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci::detail
{
/*
Breaks up a large property exchange message into chunks of the requested size.
Note that the header *must* fit inside the first block, so you must ensure
that the header is small enough to fit inside the requested chunk size.
*/
class PropertyDataMessageChunker
{
auto tie() const { return std::tie (storage, body, source, dest, chunkSize, messageKind, requestId); }
public:
/* Constructs a chunker instance.
@param storageIn backing storage where each chunk will be written
@param chunkSizeIn the maximum size of each chunk
@param messageKindIn the subID2 byte identifying the type of message in each chunk
@param requestIdIn the id that should be included in all messages that are part of the same property exchange transaction
@param headerIn the header bytes of the message. This is always JSON encoded as 7-bit ASCII text, see the MIDI-CI spec for full details
@param sourceIn the MUID of the device sending the chunked messages
@param destIn the MUID of the recipient of the chunked messages
@param bodyIn a stream that can supply the data payload for this chunk sequence. All payload bytes *must* be 7-bit (MSB not set).
*/
PropertyDataMessageChunker (std::vector<std::byte>& storageIn,
int chunkSizeIn,
const std::byte messageKindIn,
const std::byte requestIdIn,
Span<const std::byte> headerIn,
MUID sourceIn,
MUID destIn,
InputStream& bodyIn);
/* Returns true if this chunker hasn't finished producing chunks. */
explicit operator bool() const { return *this != PropertyDataMessageChunker(); }
/* Allowing foreach usage. */
auto begin() const { return *this; }
/* Allow foreach usage. */
auto end() const { return PropertyDataMessageChunker{}; }
/* Writes the bytes of the next chunk, if any, into the storage buffer. */
PropertyDataMessageChunker& operator++() noexcept;
/* Checks whether the state of this chunker matches the state of another chunker, enabling foreach usage. */
bool operator== (const PropertyDataMessageChunker& other) const noexcept { return tie() == other.tie(); }
bool operator!= (const PropertyDataMessageChunker& other) const noexcept { return tie() != other.tie(); }
/* Returns a span over the valid bytes in the output buffer. */
Span<const std::byte> operator*() const noexcept;
private:
PropertyDataMessageChunker() = default;
Span<const std::byte> getHeaderForBlock() const;
int getRoomForBody() const;
bool hasRoomForBody() const;
void populateStorage() const;
Span<const std::byte> header;
std::vector<std::byte>* storage{};
InputStream* body{};
MUID source = MUID::makeUnchecked (0), dest = MUID::makeUnchecked (0);
int chunkSize{};
uint16_t thisChunk { 0x01 };
std::byte messageKind{};
std::byte requestId{};
};
} // namespace juce::midi_ci::detail

View file

@ -0,0 +1,78 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci::detail
{
struct PropertyHostUtils
{
PropertyHostUtils() = delete;
static void send (BufferOutput& output,
uint8_t group,
std::byte subID2,
MUID targetMuid,
std::byte requestID,
Span<const std::byte> header,
Span<const std::byte> body,
int chunkSize)
{
MemoryInputStream stream (body.data(), body.size(), false);
const detail::PropertyDataMessageChunker chunker { output.getOutputBuffer(),
std::min (chunkSize, 1 << 16),
subID2,
requestID,
header,
output.getMuid(),
targetMuid,
stream };
std::for_each (chunker.begin(), chunker.end(), [&] (auto) { output.send (group); });
}
static auto getTerminator (BufferOutput& output, FunctionBlock fb, MUID them)
{
const auto us = output.getMuid();
return [&output, fb, us, them] (std::byte id)
{
const Message::Header notifyHeader
{
ChannelInGroup::wholeBlock,
detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
detail::MessageMeta::implementationVersion,
us,
them,
};
const auto jsonHeader = Encodings::jsonTo7BitText (JSONUtils::makeObjectWithKeyFirst ({ { "status", 104 } }, "status"));
detail::MessageTypeUtils::send (output,
fb.firstGroup,
notifyHeader,
Message::PropertyNotify { { id, jsonHeader, 1, 1, {} } });
};
}
};
} // namespace juce::midi_ci::detail

View file

@ -0,0 +1,507 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci::detail
{
Parser::Status Responder::processCompleteMessage (BufferOutput& output,
ump::BytesOnGroup message,
Span<ResponderDelegate* const> listeners)
{
auto status = Parser::Status::noError;
const auto parsed = Parser::parse (output.getMuid(), message.bytes, &status);
if (! parsed.has_value())
return Parser::Status::malformed;
class Output : public ResponderOutput
{
public:
Output (BufferOutput& o, Message::Header h, uint8_t g)
: innerOutput (o), header (h), group (g) {}
MUID getMuid() const override { return innerOutput.getMuid(); }
Message::Header getIncomingHeader() const override { return header; }
uint8_t getIncomingGroup() const override { return group; }
std::vector<std::byte>& getOutputBuffer() override { return innerOutput.getOutputBuffer(); }
void send (uint8_t g) override { innerOutput.send (g); }
private:
BufferOutput& innerOutput;
Message::Header header;
uint8_t group{};
};
Output responderOutput { output, parsed->header, message.group };
if (status != Parser::Status::noError)
{
switch (status)
{
case Parser::Status::collidingMUID:
{
const Message::Header header { ChannelInGroup::wholeBlock,
MessageMeta::Meta<Message::InvalidateMUID>::subID2,
MessageMeta::implementationVersion,
output.getMuid(),
MUID::getBroadcast() };
const Message::InvalidateMUID body { output.getMuid() };
MessageTypeUtils::send (responderOutput, responderOutput.getIncomingGroup(), header, body);
break;
}
case Parser::Status::unrecognisedMessage:
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x01 });
break;
case Parser::Status::reservedVersion:
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x02 });
break;
case Parser::Status::malformed:
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x41 });
break;
case Parser::Status::mismatchedMUID:
case Parser::Status::noError:
break;
}
return status;
}
for (auto* listener : listeners)
if (listener != nullptr && listener->tryRespond (responderOutput, *parsed))
return Parser::Status::noError;
MessageTypeUtils::BaseCaseDelegate base;
if (base.tryRespond (responderOutput, *parsed))
return Parser::Status::noError;
return Parser::Status::unrecognisedMessage;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class ResponderTests : public UnitTest
{
public:
ResponderTests() : UnitTest ("Responder", UnitTestCategories::midi) {}
void runTest() override
{
auto random = getRandom();
std::vector<std::byte> outgoing;
const auto makeOutput = [&]
{
struct Output : public BufferOutput
{
Output (Random& r, std::vector<std::byte>& b)
: muid (MUID::makeRandom (r)), buf (b) {}
MUID getMuid() const override { return muid; }
std::vector<std::byte>& getOutputBuffer() override { return buf; }
void send (uint8_t) override { sent.push_back (buf); }
MUID muid;
std::vector<std::byte>& buf;
std::vector<std::vector<std::byte>> sent;
};
return Output { random, outgoing };
};
beginTest ("An endpoint message with a matching MUID provokes an endpoint response");
{
constexpr auto version = MessageMeta::implementationVersion;
auto output = makeOutput();
const auto initialMUID = output.getMuid();
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
const Message::Parsed expectedInput { Message::Header { ChannelInGroup::wholeBlock,
std::byte { 0x72 },
version,
MUID::makeUnchecked (0x80c101),
initialMUID },
Message::EndpointInquiry { std::byte { 0x00 } } };
EndpointResponderListener listener;
processCompleteMessage (output, { 0, bytes }, listener);
expect (listener == SilentResponderListener (expectedInput));
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint reply */ 0x73,
/* version */ version,
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* status */ 0x00,
/* 16-bit length of following data */ 0x04,
/* ... */ 0x00,
/* info */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("An endpoint message directed at a different MUID does not provoke a response");
{
const auto destMUID = MUID::makeRandom (random);
constexpr auto version = MessageMeta::implementationVersion;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (destMUID.get() >> 0x00) & 0x7f,
/* ... */ (destMUID.get() >> 0x07) & 0x7f,
/* ... */ (destMUID.get() >> 0x0e) & 0x7f,
/* ... */ (destMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
auto output = makeOutput();
EndpointResponderListener listener;
processCompleteMessage (output, { 0, bytes }, listener);
expect (listener == SilentResponderListener());
expect (output.sent.empty());
}
beginTest ("If the listener fails to compose an endpoint response, a NAK is emitted");
{
auto output = makeOutput();
const auto initialMUID = output.getMuid();
SilentResponderListener listener;
constexpr auto version = MessageMeta::implementationVersion;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
processCompleteMessage (output, { 0, bytes }, listener);
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ version,
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* original transaction sub-id #2 */ 0x72,
/* nak status code */ 0x00,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("If a message is sent with reserved bits set in the Message Format Version, a NAK is emitted");
{
auto output = makeOutput();
const auto initialMUID = output.getMuid();
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version, reserved bit set */ 0x12,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
SilentResponderListener listener;
processCompleteMessage (output, { 0, bytes }, listener);
expect (listener == SilentResponderListener{});
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ MessageMeta::implementationVersion,
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* original transaction sub-id #2 */ 0x72,
/* nak status code */ 0x02,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("If the message body is malformed, a NAK with a status of 0x41 is emitted");
{
const auto sourceMUID = MUID::makeRandom (random);
Message::Header header;
header.deviceID = ChannelInGroup::wholeBlock;
header.category = std::byte { 0x7e };
header.version = MessageMeta::implementationVersion;
header.source = sourceMUID;
header.destination = MUID::getBroadcast();
Message::InvalidateMUID invalidate;
invalidate.target = MUID::makeRandom (random);
std::vector<std::byte> message;
Marshalling::Writer { message } (header, invalidate);
// Remove a byte from the end of the message
message.pop_back();
auto output = makeOutput();
const auto ourMUID = output.getMuid();
SilentResponderListener listener;
processCompleteMessage (output, { 0, message }, listener);
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ MessageMeta::implementationVersion,
/* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
/* ... */ (ourMUID.get() >> 0x07) & 0x7f,
/* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
/* ... */ (ourMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
/* original transaction sub-id #2 */ 0x7e,
/* nak status code */ 0x41,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("If an unrecognised message is received, a NAK with a status of 0x01 is emitted");
{
const auto sourceMUID = MUID::makeRandom (random);
Message::Header header;
header.deviceID = ChannelInGroup::wholeBlock;
header.category = std::byte { 0x50 }; // reserved
header.version = MessageMeta::implementationVersion;
header.source = sourceMUID;
header.destination = MUID::getBroadcast();
std::vector<std::byte> message;
Marshalling::Writer { message } (header);
message.emplace_back();
auto output = makeOutput();
const auto ourMUID = output.getMuid();
SilentResponderListener listener;
processCompleteMessage (output, { 0, message }, listener);
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ MessageMeta::implementationVersion,
/* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
/* ... */ (ourMUID.get() >> 0x07) & 0x7f,
/* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
/* ... */ (ourMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
/* original transaction sub-id #2 */ 0x50,
/* nak status code */ 0x01,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
}
private:
template <typename... Ts>
static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
{
jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
return { std::byte (ts)... };
}
struct SilentResponderListener : public ResponderDelegate
{
SilentResponderListener() = default;
explicit SilentResponderListener (const Message::Parsed& p) : parsed (p) {}
bool tryRespond (ResponderOutput&, const Message::Parsed& p) override
{
parsed = p;
return false;
}
// Returning false indicates that the message was not handled
bool operator== (const SilentResponderListener& other) const { return parsed == other.parsed; }
bool operator!= (const SilentResponderListener& other) const { return ! operator== (other); }
std::optional<Message::Parsed> parsed;
};
struct EndpointResponderListener : public SilentResponderListener
{
bool tryRespond (ResponderOutput& output, const Message::Parsed& message) override
{
parsed = message;
if (std::holds_alternative<Message::EndpointInquiry> (message.body))
{
std::array<std::byte, 4> data{};
Message::EndpointInquiryResponse response;
response.status = std::byte{};
response.data = data;
MessageTypeUtils::send (output, output.getIncomingGroup(), output.getReplyHeader (std::byte { 0x73 }), response);
return true;
}
return SilentResponderListener::tryRespond (output, message);
}
using SilentResponderListener::operator==, SilentResponderListener::operator!=;
};
struct OutputCallback
{
void operator() (Span<const std::byte> bytes)
{
output = std::vector<std::byte> (bytes.begin(), bytes.end());
}
std::vector<std::byte> output;
};
template <typename A, typename B>
static bool rangesEqual (A&& a, B&& b)
{
using std::begin, std::end;
return std::equal (begin (a), end (a), begin (b), end (b));
}
static Parser::Status processCompleteMessage (BufferOutput& output,
ump::BytesOnGroup message,
ResponderDelegate& listener)
{
ResponderDelegate* const listeners[] { &listener };
return Responder::processCompleteMessage (output, message, listeners);
}
};
static ResponderTests responderTests;
#endif
} // namespace juce::midi_ci::detail

View file

@ -0,0 +1,49 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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::midi_ci::detail
{
/*
Parses individual messages, and additionally gives ResponderDelegates a chance to formulate
a response to any message that would normally necessitate a reply.
*/
struct Responder
{
Responder() = delete;
/* Parses the message, then calls tryParse on each ResponderDelegate in
turn until one returns true, indicating that the message has been
handled. Most 'inquiry' messages should emit one or more reply messages.
These replies will be written to the provided BufferOutput.
If none of the provided delegates are able to handle the message, then
a generic NAK will be written to the BufferOutput.
*/
static Parser::Status processCompleteMessage (BufferOutput& output,
ump::BytesOnGroup message,
Span<ResponderDelegate* const> delegates);
};
} // namespace juce::midi_ci

View file

@ -0,0 +1,55 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#ifdef JUCE_MIDI_CI_H_INCLUDED
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
#include "juce_midi_ci.h"
#include <juce_midi_ci/detail/juce_CIMessageMeta.h>
#include <juce_midi_ci/detail/juce_CIMarshalling.h>
#include <juce_midi_ci/detail/juce_CIPropertyDataMessageChunker.h>
#include <juce_midi_ci/detail/juce_CIResponder.h>
#include <juce_midi_ci/detail/juce_CIMessageTypeUtils.h>
#include <juce_midi_ci/detail/juce_CIPropertyHostUtils.h>
#include <juce_midi_ci/detail/juce_CIPropertyDataMessageChunker.cpp>
#include <juce_midi_ci/detail/juce_CIResponder.cpp>
#include <juce_midi_ci/ci/juce_CIDevice.cpp>
#include <juce_midi_ci/ci/juce_CIEncodings.cpp>
#include <juce_midi_ci/ci/juce_CIParser.cpp>
#include <juce_midi_ci/ci/juce_CIProfileHost.cpp>
#include <juce_midi_ci/ci/juce_CIProfileStates.cpp>
#include <juce_midi_ci/ci/juce_CIPropertyDelegate.cpp>
#include <juce_midi_ci/ci/juce_CIPropertyExchangeCache.cpp>
#include <juce_midi_ci/ci/juce_CIPropertyHost.cpp>
#include <juce_midi_ci/ci/juce_CIResponderOutput.cpp>

View file

@ -0,0 +1,85 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_midi_ci
vendor: juce
version: 7.0.8
name: JUCE MIDI CI Classes
description: Classes facilitating communication via MIDI Capability Inquiry
website: http://www.juce.com/juce
license: GPL/Commercial
minimumCppStandard: 17
dependencies: juce_audio_basics
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#define JUCE_MIDI_CI_H_INCLUDED
#include <juce_audio_basics/juce_audio_basics.h>
#include <juce_midi_ci/ci/juce_CIFunctionBlock.h>
#include <juce_midi_ci/ci/juce_CIMuid.h>
#include <juce_midi_ci/ci/juce_CIEncoding.h>
#include <juce_midi_ci/ci/juce_CIEncodings.h>
#include <juce_midi_ci/ci/juce_CIMessages.h>
#include <juce_midi_ci/ci/juce_CIChannelAddress.h>
#include <juce_midi_ci/ci/juce_CIResponderOutput.h>
#include <juce_midi_ci/ci/juce_CIParser.h>
#include <juce_midi_ci/ci/juce_CISupportedAndActive.h>
#include <juce_midi_ci/ci/juce_CIResponderDelegate.h>
#include <juce_midi_ci/ci/juce_CIProfileStates.h>
#include <juce_midi_ci/ci/juce_CIProfileAtAddress.h>
#include <juce_midi_ci/ci/juce_CIProfileDelegate.h>
#include <juce_midi_ci/ci/juce_CIProfileHost.h>
#include <juce_midi_ci/ci/juce_CISubscription.h>
#include <juce_midi_ci/ci/juce_CIPropertyDelegate.h>
#include <juce_midi_ci/ci/juce_CIPropertyExchangeResult.h>
#include <juce_midi_ci/ci/juce_CIPropertyExchangeCache.h>
#include <juce_midi_ci/ci/juce_CIPropertyHost.h>
#include <juce_midi_ci/ci/juce_CIDeviceFeatures.h>
#include <juce_midi_ci/ci/juce_CIDeviceMessageHandler.h>
#include <juce_midi_ci/ci/juce_CIDeviceOptions.h>
#include <juce_midi_ci/ci/juce_CIDeviceListener.h>
#include <juce_midi_ci/ci/juce_CIDevice.h>
namespace juce
{
namespace ci = midi_ci;
}