1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 namespace BlocksProtocol
26 {
27 
28 /** This value is incremented when the format of the API changes in a way which
29     breaks compatibility.
30 */
31 static constexpr uint32 currentProtocolVersion = 1;
32 
33 using ProtocolVersion = IntegerWithBitSize<8>;
34 
35 //==============================================================================
36 /** A timestamp for a packet, in milliseconds since device boot-up */
37 using PacketTimestamp = IntegerWithBitSize<32>;
38 
39 /** This relative timestamp is for use inside a packet, and it represents a
40     number of milliseconds that should be added to the packet's timestamp.
41 */
42 using PacketTimestampOffset = IntegerWithBitSize<5>;
43 
44 
45 //==============================================================================
46 /** Messages that a device may send to the host. */
47 enum class MessageFromDevice
48 {
49     deviceTopology          = 0x01,
50     packetACK               = 0x02,
51     firmwareUpdateACK       = 0x03,
52     deviceTopologyExtend    = 0x04,
53     deviceTopologyEnd       = 0x05,
54     deviceVersion           = 0x06,
55     deviceName              = 0x07,
56 
57     touchStart              = 0x10,
58     touchMove               = 0x11,
59     touchEnd                = 0x12,
60 
61     touchStartWithVelocity  = 0x13,
62     touchMoveWithVelocity   = 0x14,
63     touchEndWithVelocity    = 0x15,
64 
65     configMessage           = 0x18,
66 
67     controlButtonDown       = 0x20,
68     controlButtonUp         = 0x21,
69 
70     programEventMessage     = 0x28,
71 
72     logMessage              = 0x30
73 };
74 
75 /** Messages that the host may send to a device. */
76 enum class MessageFromHost
77 {
78     deviceCommandMessage    = 0x01,
79     sharedDataChange        = 0x02,
80     programEventMessage     = 0x03,
81     firmwareUpdatePacket    = 0x04,
82 
83     configMessage           = 0x10,
84     factoryReset            = 0x11,
85     blockReset              = 0x12,
86 
87     setName                 = 0x20
88 };
89 
90 /** Messages that the host may send to a device that do not have the usual message format */
91 namespace SpecialMessageFromHost
92 {
93     constexpr uint8 resetMaster[6] = { 0xf0, 0x00, 0x21, 0x10, 0x49, 0xf7 };
94 }
95 
96 /** This is the first item in a BLOCKS message, identifying the message type. */
97 using MessageType = IntegerWithBitSize<7>;
98 
99 //==============================================================================
100 /** This is a type of index identifier used to refer to a block within a group.
101     It refers to the index of a device in the list of devices that was most recently
102     sent via a topology change message
103     (It's not a global UID for a block unit).
104     NB: to send a message to all devices, pass the getDeviceIndexForBroadcast() value.
105 */
106 using TopologyIndex     = uint8;
107 
108 static constexpr int topologyIndexBits = 7;
109 
110 /** Use this value as the index if you want a message to be sent to all devices in
111     the group.
112 */
113 static constexpr TopologyIndex topologyIndexForBroadcast = 63;
114 
115 using DeviceCount       = IntegerWithBitSize<7>;
116 using ConnectionCount   = IntegerWithBitSize<8>;
117 
118 //==============================================================================
119 /** Battery charge level. */
120 using BatteryLevel      = IntegerWithBitSize<5>;
121 
122 /** Battery charger connection flag. */
123 using BatteryCharging   = IntegerWithBitSize<1>;
124 
125 //==============================================================================
126 /** ConnectorPort is an index, starting at 0 for the leftmost port on the
127     top edge, and going clockwise.
128 */
129 using ConnectorPort = IntegerWithBitSize<5>;
130 
131 //==============================================================================
132 /** Structure for generic block data
133 
134     @tags{Blocks}
135  */
136 template <size_t MaxSize>
137 struct BlockStringData
138 {
139     uint8 data[MaxSize] = {};
140     uint8 length = 0;
141 
142     static const size_t maxLength { MaxSize };
143 
isNotEmptyBlockStringData144     bool isNotEmpty() const
145     {
146         return length > 0;
147     }
148 
asStringBlockStringData149     String asString() const
150     {
151         return String ((const char*) data, length);
152     }
153 
154     bool operator== (const BlockStringData& other) const
155     {
156         if (length != other.length)
157             return false;
158 
159         for (int i = 0; i < length; ++i)
160             if (data[i] != other.data[i])
161                 return false;
162 
163         return true;
164     }
165 
166     bool operator!= (const BlockStringData& other) const
167     {
168         return ! ( *this == other );
169     }
170 };
171 
172 using VersionNumber = BlockStringData<21>;
173 using BlockName = BlockStringData<33>;
174 
175 //==============================================================================
176 /** Structure describing a block's serial number
177 
178     @tags{Blocks}
179 */
180 struct BlockSerialNumber : public BlockStringData<16>
181 {
isValidBlockSerialNumber182     bool isValid() const noexcept
183     {
184         for (auto c : data)
185             if (c == 0)
186                 return false;
187 
188         return isAnyControlBlock() || isPadBlock() || isSeaboardBlock() || isLumiKeysBlock();
189     }
190 
isPadBlockBlockSerialNumber191     bool isPadBlock() const noexcept            { return hasPrefix ("LPB") || hasPrefix ("LPM"); }
isLiveBlockBlockSerialNumber192     bool isLiveBlock() const noexcept           { return hasPrefix ("LIC"); }
isLoopBlockBlockSerialNumber193     bool isLoopBlock() const noexcept           { return hasPrefix ("LOC"); }
isDevCtrlBlockBlockSerialNumber194     bool isDevCtrlBlock() const noexcept        { return hasPrefix ("DCB"); }
isTouchBlockBlockSerialNumber195     bool isTouchBlock() const noexcept          { return hasPrefix ("TCB"); }
isSeaboardBlockBlockSerialNumber196     bool isSeaboardBlock() const noexcept       { return hasPrefix ("SBB"); }
isLumiKeysBlockBlockSerialNumber197     bool isLumiKeysBlock() const noexcept       { return hasPrefix ("LKB"); }
198 
isAnyControlBlockBlockSerialNumber199     bool isAnyControlBlock() const noexcept     { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock() || isTouchBlock(); }
200 
hasPrefixBlockSerialNumber201     bool hasPrefix (const char* prefix) const noexcept  { return memcmp (data, prefix, 3) == 0; }
202 };
203 
204 //==============================================================================
205 /** Structure for the device status
206 
207     @tags{Blocks}
208 */
209 struct DeviceStatus
210 {
211     BlockSerialNumber serialNumber;
212     TopologyIndex index;
213     BatteryLevel batteryLevel;
214     BatteryCharging batteryCharging;
215 };
216 
217 //==============================================================================
218 /** Structure for the device connection
219 
220     @tags{Blocks}
221 */
222 struct DeviceConnection
223 {
224     TopologyIndex device1, device2;
225     ConnectorPort port1, port2;
226 
227     bool operator== (const DeviceConnection& other) const
228     {
229         return isEqual (other);
230     }
231 
232     bool operator!= (const DeviceConnection& other) const
233     {
234         return ! isEqual (other);
235     }
236 
237 private:
isEqualDeviceConnection238     bool isEqual (const DeviceConnection& other) const
239     {
240         return device1 == other.device1
241             && device2 == other.device2
242             && port1 == other.port1
243             && port2 == other.port2;
244     }
245 };
246 
247 //==============================================================================
248 /** Structure for the device version
249 
250     @tags{Blocks}
251 */
252 struct DeviceVersion
253 {
254     TopologyIndex index;
255     VersionNumber version;
256 };
257 
258 //==============================================================================
259 /** Structure used for the device name
260 
261     @tags{Blocks}
262 */
263 struct DeviceName
264 {
265     TopologyIndex index;
266     BlockName name;
267 };
268 
269 static constexpr uint8 maxBlocksInTopologyPacket = 6;
270 static constexpr uint8 maxConnectionsInTopologyPacket = 24;
271 
272 //==============================================================================
273 /** Configuration Item Identifiers. */
274 enum ConfigItemId
275 {
276     // MIDI
277     midiStartChannel    = 0,
278     midiEndChannel      = 1,
279     midiUseMPE          = 2,
280     pitchBendRange      = 3,
281     octave              = 4,
282     transpose           = 5,
283     slideCC             = 6,
284     slideMode           = 7,
285     octaveTopology      = 8,
286     midiChannelRange    = 9,
287     MPEZone             = 40,
288     // Touch
289     velocitySensitivity = 10,
290     glideSensitivity    = 11,
291     slideSensitivity    = 12,
292     pressureSensitivity = 13,
293     liftSensitivity     = 14,
294     fixedVelocity       = 15,
295     fixedVelocityValue  = 16,
296     pianoMode           = 17,
297     glideLock           = 18,
298     glideLockEnable     = 19,
299     // Live
300     mode                = 20,
301     volume              = 21,
302     scale               = 22,
303     hideMode            = 23,
304     chord               = 24,
305     arpPattern          = 25,
306     tempo               = 26,
307     key                 = 27,
308     autoTransposeToKey  = 28,
309     // Tracking
310     xTrackingMode       = 30,
311     yTrackingMode       = 31,
312     zTrackingMode       = 32,
313     // Graphics
314     gammaCorrection     = 33,
315     globalKeyColour     = 34,
316     rootKeyColour       = 35,
317     brightness          = 36,
318     // User
319     user0               = 64,
320     user1               = 65,
321     user2               = 66,
322     user3               = 67,
323     user4               = 68,
324     user5               = 69,
325     user6               = 70,
326     user7               = 71,
327     user8               = 72,
328     user9               = 73,
329     user10              = 74,
330     user11              = 75,
331     user12              = 76,
332     user13              = 77,
333     user14              = 78,
334     user15              = 79,
335     user16              = 80,
336     user17              = 81,
337     user18              = 82,
338     user19              = 83,
339     user20              = 84,
340     user21              = 85,
341     user22              = 86,
342     user23              = 87,
343     user24              = 88,
344     user25              = 89,
345     user26              = 90,
346     user27              = 91,
347     user28              = 92,
348     user29              = 93,
349     user30              = 94,
350     user31              = 95
351 };
352 
353 static constexpr uint8 numberOfUserConfigs = 32;
354 static constexpr uint8 maxConfigIndex = uint8 (ConfigItemId::user0) + numberOfUserConfigs;
355 
356 static constexpr uint8 configUserConfigNameLength = 32;
357 static constexpr uint8 configMaxOptions = 16;
358 static constexpr uint8 configOptionNameLength = 16;
359 
360 //==============================================================================
361 /** The coordinates of a touch.
362 
363     @tags{Blocks}
364 */
365 struct TouchPosition
366 {
367     using Xcoord = IntegerWithBitSize<12>;
368     using Ycoord = IntegerWithBitSize<12>;
369     using Zcoord = IntegerWithBitSize<8>;
370 
371     Xcoord x;
372     Ycoord y;
373     Zcoord z;
374 
375     enum { bits = Xcoord::bits + Ycoord::bits + Zcoord::bits };
376 };
377 
378 /** The velocities for each dimension of a touch.
379 
380     @tags{Blocks}
381 */
382 struct TouchVelocity
383 {
384     using VXcoord = IntegerWithBitSize<8>;
385     using VYcoord = IntegerWithBitSize<8>;
386     using VZcoord = IntegerWithBitSize<8>;
387 
388     VXcoord vx;
389     VYcoord vy;
390     VZcoord vz;
391 
392     enum { bits = VXcoord::bits + VYcoord::bits + VZcoord::bits };
393 };
394 
395 /** The index of a touch, i.e. finger number. */
396 using TouchIndex = IntegerWithBitSize<5>;
397 
398 using PacketCounter = IntegerWithBitSize<10>;
399 
400 //==============================================================================
401 enum DeviceCommands
402 {
403     beginAPIMode                = 0x00,
404     requestTopologyMessage      = 0x01,
405     endAPIMode                  = 0x02,
406     ping                        = 0x03,
407     debugMode                   = 0x04,
408     saveProgramAsDefault        = 0x05
409 };
410 
411 using DeviceCommand = IntegerWithBitSize<9>;
412 
413 //==============================================================================
414 enum ConfigCommands
415 {
416     setConfig                   = 0x00,
417     requestConfig               = 0x01, // Request a config update
418     requestFactorySync          = 0x02, // Requests all active factory config data
419     requestUserSync             = 0x03, // Requests all active user config data
420     updateConfig                = 0x04, // Set value, min and max
421     updateUserConfig            = 0x05, // As above but contains user config metadata
422     setConfigState              = 0x06, // Set config activation state and whether it is saved in flash
423     factorySyncEnd              = 0x07,
424     clusterConfigSync           = 0x08,
425     factorySyncReset            = 0x09
426 };
427 
428 using ConfigCommand = IntegerWithBitSize<4>;
429 using ConfigItemIndex = IntegerWithBitSize<8>;
430 using ConfigItemValue = IntegerWithBitSize<32>;
431 
432 //==============================================================================
433 /** An ID for a control-block button type */
434 using ControlButtonID = IntegerWithBitSize<12>;
435 
436 //==============================================================================
437 using RotaryDialIndex = IntegerWithBitSize<7>;
438 using RotaryDialAngle = IntegerWithBitSize<14>;
439 using RotaryDialDelta = IntegerWithBitSize<14>;
440 
441 //==============================================================================
442 enum DataChangeCommands
443 {
444     endOfPacket                 = 0,
445     endOfChanges                = 1,
446     skipBytesFew                = 2,
447     skipBytesMany               = 3,
448     setSequenceOfBytes          = 4,
449     setFewBytesWithValue        = 5,
450     setFewBytesWithLastValue    = 6,
451     setManyBytesWithValue       = 7
452 };
453 
454 using PacketIndex            = IntegerWithBitSize<16>;
455 using DataChangeCommand      = IntegerWithBitSize<3>;
456 using ByteCountFew           = IntegerWithBitSize<4>;
457 using ByteCountMany          = IntegerWithBitSize<8>;
458 using ByteValue              = IntegerWithBitSize<8>;
459 using ByteSequenceContinues  = IntegerWithBitSize<1>;
460 
461 using FirmwareUpdateACKCode    = IntegerWithBitSize<7>;
462 using FirmwareUpdateACKDetail  = IntegerWithBitSize<32>;
463 using FirmwareUpdatePacketSize = IntegerWithBitSize<7>;
464 
465 static constexpr uint32 numProgramMessageInts = 3;
466 
467 static constexpr uint32 apiModeHostPingTimeoutMs = 5000;
468 
469 static constexpr uint32 padBlockProgramAndHeapSize = 7200;
470 static constexpr uint32 padBlockStackSize = 800;
471 
472 static constexpr uint32 controlBlockProgramAndHeapSize = 3000;
473 static constexpr uint32 controlBlockStackSize = 800;
474 
475 
476 //==============================================================================
477 /** Contains the number of bits required to encode various items in the packets */
478 enum BitSizes
479 {
480     topologyMessageHeader    = (int) MessageType::bits + (int) ProtocolVersion::bits + (int) DeviceCount::bits + (int) ConnectionCount::bits,
481     topologyDeviceInfo       = (int) BlockSerialNumber::maxLength * 7 + (int) BatteryLevel::bits + (int) BatteryCharging::bits,
482     topologyConnectionInfo   = topologyIndexBits + (int) ConnectorPort::bits + topologyIndexBits + (int) ConnectorPort::bits,
483 
484     typeDeviceAndTime        = (int) MessageType::bits + (int) PacketTimestampOffset::bits,
485 
486     touchMessage             = (int) typeDeviceAndTime + (int) TouchIndex::bits + (int) TouchPosition::bits,
487     touchMessageWithVelocity = (int) touchMessage + (int) TouchVelocity::bits,
488 
489     programEventMessage      = (int) MessageType::bits + 32 * numProgramMessageInts,
490     packetACK                = (int) MessageType::bits + (int) PacketCounter::bits,
491 
492     firmwareUpdateACK        = (int) MessageType::bits + (int) FirmwareUpdateACKCode::bits + (int) FirmwareUpdateACKDetail::bits,
493 
494     controlButtonMessage     = (int) typeDeviceAndTime + (int) ControlButtonID::bits,
495 
496     configSetMessage         = (int) MessageType::bits + (int) ConfigCommand::bits + (int) ConfigItemIndex::bits + (int) ConfigItemValue::bits,
497     configRespMessage        = (int) MessageType::bits + (int) ConfigCommand::bits + (int) ConfigItemIndex::bits + ((int) ConfigItemValue::bits * 3),
498     configSyncEndMessage     = (int) MessageType::bits + (int) ConfigCommand::bits,
499 };
500 
501 //==============================================================================
502 // These are the littlefoot functions provided for use in BLOCKS programs
503 static constexpr const char* ledProgramLittleFootFunctions[] =
504 {
505     "min/iii",
506     "min/fff",
507     "max/iii",
508     "max/fff",
509     "clamp/iiii",
510     "clamp/ffff",
511     "abs/ii",
512     "abs/ff",
513     "map/ffffff",
514     "map/ffff",
515     "mod/iii",
516     "getRandomFloat/f",
517     "getRandomInt/ii",
518     "log/vi",
519     "logHex/vi",
520     "getMillisecondCounter/i",
521     "getFirmwareVersion/i",
522     "getTimeInCurrentFunctionCall/i",
523     "getBatteryLevel/f",
524     "isBatteryCharging/b",
525     "isMasterBlock/b",
526     "isConnectedToHost/b",
527     "setStatusOverlayActive/vb",
528     "getNumBlocksInTopology/i",
529     "getBlockIDForIndex/ii",
530     "getBlockIDOnPort/ii",
531     "getPortToMaster/i",
532     "getBlockTypeForID/ii",
533     "sendMessageToBlock/viiii",
534     "sendMessageToHost/viii",
535     "getHorizontalDistFromMaster/i",
536     "getVerticalDistFromMaster/i",
537     "getAngleFromMaster/i",
538     "setAutoRotate/vb",
539     "getClusterIndex/i",
540     "getClusterWidth/i",
541     "getClusterHeight/i",
542     "getClusterXpos/i",
543     "getClusterYpos/i",
544     "getNumBlocksInCurrentCluster/i",
545     "getBlockIdForBlockInCluster/ii",
546     "isMasterInCurrentCluster/b",
547     "setClusteringActive/vb",
548     "makeARGB/iiiii",
549     "blendARGB/iii",
550     "fillPixel/viii",
551     "blendPixel/viii",
552     "fillRect/viiiii",
553     "blendRect/viiiii",
554     "blendGradientRect/viiiiiiii",
555     "blendCircle/vifffb",
556     "addPressurePoint/vifff",
557     "drawPressureMap/v",
558     "fadePressureMap/v",
559     "drawNumber/viiii",
560     "clearDisplay/v",
561     "clearDisplay/vi",
562     "displayBatteryLevel/v",
563     "sendMIDI/vi",
564     "sendMIDI/vii",
565     "sendMIDI/viii",
566     "sendNoteOn/viii",
567     "sendNoteOff/viii",
568     "sendAftertouch/viii",
569     "sendCC/viii",
570     "sendPitchBend/vii",
571     "sendPitchBend/viii",
572     "sendChannelPressure/vii",
573     "addPitchCorrectionPad/viiffff",
574     "setPitchCorrectionEnabled/vb",
575     "getPitchCorrectionPitchBend/iii",
576     "setChannelRange/vbii",
577     "assignChannel/ii",
578     "deassignChannel/vii",
579     "getControlChannel/i",
580     "useMPEDuplicateFilter/vb",
581     "getSensorValue/iii",
582     "handleTouchAsSeaboard/vi",
583     "setPowerSavingEnabled/vb",
584     "getLocalConfig/ii",
585     "setLocalConfig/vii",
586     "requestRemoteConfig/vii",
587     "setRemoteConfig/viii",
588     "setLocalConfigItemRange/viii",
589     "setLocalConfigActiveState/vibb",
590     "linkBlockIDtoController/vi",
591     "repaintControl/v",
592     "onControlPress/vi",
593     "onControlRelease/vi",
594     "initControl/viiiiiiiii",
595     "setButtonMode/vii",
596     "setButtonType/viii",
597     "setButtonMinMaxDefault/viiii",
598     "setButtonColours/viii",
599     "setButtonTriState/vii",
600     "padControllerInitDefault/vb",
601     "padControllerReset/v",
602     "padControllerRegenDefault/v",
603     "padControllerRepaint/v",
604     "padControllerDrawPad/vi",
605     "setUseDefaultKeyHandler/vb",
606     "setUseDefaultKeyHandler/vbb",
607 
608     nullptr
609 };
610 
611 } // namespace BlocksProtocol
612 } // namespace juce
613