diff --git a/Factory Scripts/BlockBOARD.littlefoot b/Factory Scripts/BlockBOARD.littlefoot
new file mode 100644
index 0000000..cf9ee50
--- /dev/null
+++ b/Factory Scripts/BlockBOARD.littlefoot
@@ -0,0 +1,1407 @@
+/*
+
+
+*/
+
+#heapsize: 849
+
+//==============================================================================
+/*
+ Heap layout:
+
+ === 7x7 = 49 Key Pad ===
+
+ 0-196 4 byte x 49 colours
+
+ === 24 x Touch ===
+
+ 196 1 byte x 49 corresponding pad index (0xff if none)
+ 245 4 byte x 49 initial x positions (for relative pitchbend)
+ 441 4 byte x 49 initial y positions (for relative y axis)
+ 637 1 byte x 49 MIDI channel assigned
+
+ === 16 x Channel ===
+
+ 686 1 byte x 16 touch to track for this channel (depends on tracking mode)
+
+ === 24 x Touch ===
+
+ 702 1 byte x 49 Touch note number
+ 751 1 byte x 49 Touch velocity
+ 800 1 byte x 49 list of active touch indicies in order of first played to last played
+
+ Parameters:
+
+ // 0 MIDI Start Channel
+ // 1 MIDI End Channel
+ // 2 Use MPE
+ // 4 Octave
+ // 5 Transpose
+ // 6 Slide CC
+ // 7 Slide Mode
+ // 8
+ // 9
+ // 10
+ // 11
+ // 12
+ // 13
+ // 14
+ // 15 Fixed Velocity
+ // 16 Fixed Velocity Value
+ // 17 Piano Mode
+ // 18 Glide Rate
+ // 19 Glidelock Enabled
+ // 20 Mode / GridSize
+ // 21
+ // 22 Scale
+ // 23 Hide Mode
+ // 24 Chord
+ // 25
+ // 26
+ // 27
+ // 28
+ // 29
+ // 30 Glide Tracking Mode
+ // 31 Pitch Bend Rate
+ // 32 Pressure Tracking Mode
+
+*/
+//==============================================================================
+
+const int purple = 0x5856d6;
+const int blue = 0x007aff;
+const int midblue = 0x34aadc;
+const int lightblue = 0x5ad8fa;
+const int green = 0x4cd964;
+const int pink = 0xff2d55;
+const int red = 0xff3b30;
+const int orange = 0xff9500;
+const int palegreen = 0xffcc0;
+
+const int gradientColour1 = 0x7199ff;
+const int gradientColour2 = 0x6fe6ff;
+
+const int padHighColour = 0xff366CC5;
+const int padLowColour = 0xffAA429A;
+
+
+//==============================================================================
+
+int gridSize, padWidth, padSpacing;
+int dimFactor, dimDelay, dimDelta;
+int scaleBitmask;
+int numNotesInScale;
+int channelLastAssigned;
+int activePads;
+int pitchbendRange;
+int octave;
+int xShift, yShift;
+int transpose;
+int scale; // Major;Minor;Harmonic Minor;Pentatonic Neutral;Pentatonic Major;Pentatonic Minor;Blues;Dorian;Phrygian;Lydian;Mixolydian;Locrian;Whole Tone;Arabic (A);Arabic (B);Japanese;Ryukyu;8-Tone Spanish;Chromatic;
+int chord; // Octave;Fifth;Major;Minor
+bool hideMode;
+int position; // Split into X and Y offsets
+int clusterWidth;
+int clusterHeight;
+int xPos;
+int yPos;
+bool armTouchCommands;
+
+bool glideLockEnabled;
+int glideLockInitialNote;
+float glideLockTarget;
+int glideLockChannel;
+
+bool gammaCorrected;
+
+//==============================================================================
+void Pad_setColour (int padIndex, int colour) { setHeapInt ((padIndex * 4), colour); }
+int Pad_getColour (int padIndex) { return getHeapInt (padIndex * 4); }
+//void Pad_setNote (int padIndex, int note) { /* setHeapByte (padIndex + xxx, note); */}
+//int Pad_getNote (int padIndex) { return getHeapByte (padIndex + xxx); }
+void Pad_setActive (int padIndex, bool setActive) { activePads = setActive ? (activePads | (1 << padIndex)) : (activePads & ~(1 << padIndex)); }
+bool Pad_isActive (int padIndex) { return activePads & (1 << padIndex); }
+bool isAnyPadActive() { return activePads; }
+
+//==============================================================================
+void Touch_setPad (int touchIndex, int padIndex) { setHeapByte (touchIndex + 196, padIndex); }
+int Touch_getPad (int touchIndex) { return getHeapByte (touchIndex + 196); }
+void Touch_setInitialX (int touchIndex, float initialX) { setHeapInt ((touchIndex * 4) + 245, int (initialX * 1e6)); }
+float Touch_getInitialX (int touchIndex) { return float (getHeapInt ((touchIndex * 4) + 245)) / 1e6; }
+void Touch_setInitialY (int touchIndex, float initialY) { setHeapInt ((touchIndex * 4) + 441, int (initialY * 1e6)); }
+float Touch_getInitialY (int touchIndex) { return float (getHeapInt ((touchIndex * 4) + 441)) / 1e6; }
+void Touch_setChannel (int touchIndex, int channel) { setHeapByte (touchIndex + 637, channel); }
+int Touch_getChannel (int touchIndex) { return getHeapByte (touchIndex + 637); }
+void Touch_setNote (int touchIndex, int noteNumber) { setHeapByte (touchIndex + 702, noteNumber); }
+int Touch_getNote (int touchIndex) { return getHeapByte (touchIndex + 702); }
+void Touch_setVelocity (int touchIndex, int velocity) { setHeapByte (touchIndex + 751, velocity); }
+int Touch_getVelocity (int touchIndex) { return getHeapByte (touchIndex + 751); }
+void Touch_setTouchByHistory (int touchIndex, int order){ setHeapByte (order + 800, touchIndex); }
+int Touch_getTouchByHistory (int order) { return getHeapByte (order + 800); }
+
+//==============================================================================
+
+void Channel_setTrackedTouch (int channel, int touchIndex)
+{
+ setHeapByte (channel + 686, touchIndex);
+}
+
+int Channel_getTrackedTouch (int channel)
+{
+ return getHeapByte (channel + 686);
+}
+
+//==============================================================================
+bool isPartOfScale (int noteRelativeToTonic)
+{
+ int noteAsBitSet = 0x01 << (mod (noteRelativeToTonic, 12));
+ return (noteAsBitSet & scaleBitmask) != 0;
+}
+
+int findNthNoteInScale (int n)
+{
+ int count = 0;
+
+ for (int pos = 0; pos < 12; ++pos)
+ {
+ if (scaleBitmask & (0x01 << pos))
+ {
+ if (count == n)
+ return pos;
+
+ count++;
+ }
+ }
+
+ return -1;
+}
+
+//==============================================================================
+int getTouchedPad (float x, float y)
+{
+ int col = int (x * 0.5 * float (gridSize));
+ int row = int (y * 0.5 * float (gridSize));
+
+ return (gridSize * row) + col;
+}
+
+//==============================================================================
+int getNoteForPad (int padIndex)
+{
+ // convert pad index (starting top left) to index in note sequence (starting bottom left):
+ int lowestDefaultNote = 24;
+ int padRow = padIndex / gridSize;
+ int padCol = padIndex % gridSize;
+ int noteIndex = ((gridSize - 1) - padRow) * gridSize + padCol;
+ int topologyShift, scaleShift;
+
+ //drums have a lower default octave to melodic
+ if (gridSize < 5) lowestDefaultNote = 36;
+ else if (gridSize <= 5) lowestDefaultNote = 48;
+
+ int lowestNoteIndex = (octave * 12) + lowestDefaultNote + transpose;
+
+ if (gridSize >= 5 )
+ {
+ topologyShift = (xShift * 5) + (yShift * 25);
+
+ if (! hideMode)
+ {
+ lowestNoteIndex += topologyShift;
+ }
+ else
+ {
+ lowestNoteIndex += roundDownDivide (topologyShift, numNotesInScale) * 12;
+ scaleShift = topologyShift % numNotesInScale;
+
+ if (scaleShift < 0)
+ scaleShift += numNotesInScale;
+
+ }
+ }
+ else
+ {
+ // Drum grid
+ int notesPerRow = getClusterWidth() * gridSize;
+
+ return lowestNoteIndex + (xShift * gridSize) + padCol + (((gridSize - 1) - padRow) * notesPerRow) + (yShift * notesPerRow * gridSize);
+ }
+
+ if (! hideMode)
+ return noteIndex + lowestNoteIndex;
+
+ int note = findNthNoteInScale ((noteIndex + scaleShift) % numNotesInScale) + ((noteIndex + scaleShift) / numNotesInScale * 12) + lowestNoteIndex;
+ return note;
+}
+
+int roundDownDivide (int a, int b)
+{
+ if (a >= 0)
+ return a / b;
+ else
+ return (a - b + 1) / b;
+}
+
+//==============================================================================
+int getTrailColour (int padColour)
+{
+ if (padColour == 0xff000000)
+ return 0xffaaaaaa;
+
+ return blendARGB (0xFFFFFFFF, padColour);
+}
+
+//==============================================================================
+void updateDimFactor()
+{
+ int dimLim = gammaCorrected ? 100 : 180;
+
+ if (isAnyPadActive() || dimDelta)
+ {
+ if (dimFactor < dimLim)
+ dimDelta = 60;
+ else
+ dimDelta = 0;
+
+ dimFactor += dimDelta;
+ dimDelay = 3;
+ }
+ else
+ {
+ if (--dimDelay <= 0)
+ {
+ dimFactor -= 24;
+
+ if (dimFactor < 0)
+ dimFactor = 0;
+ }
+ }
+}
+
+//==============================================================================
+void drawPads()
+{
+ int padIndex = 0;
+
+ if (gridSize == 1)
+ drawGradient(padHighColour, padLowColour, 0, 0, 15, 15);
+ else
+ {
+ for (int padY = 0; padY < gridSize; ++padY)
+ {
+ for (int padX = 0; padX < gridSize; ++padX)
+ {
+ int overlayColour = Pad_isActive (padIndex) && gridSize > 1 ? 0x66ffffff : (dimFactor << 24);
+
+ drawPad (padX, padY, blendARGB (Pad_getColour (padIndex), overlayColour), 0xb8);
+
+ ++padIndex;
+ }
+ }
+ }
+ drawPadControls();
+}
+
+void drawPadControls() {
+
+ if (!armTouchCommands)
+ fillPixel(red, 14, 0);
+ else
+ {
+ // Draw black border
+ fillRect (0, 0, 0, 15, 1);
+ fillRect (0, 0, 0, 1, 15);
+ fillRect (0, 14, 0, 1, 15);
+ fillRect (0, 0, 14, 15, 1);
+
+ // Draw left controls
+ if (hideMode) fillRect (palegreen, 0, 1, 1, 2);
+ fillRect (lightblue, 0, 3, 1, 2);
+ fillRect (blue, 0, 5, 1, 2);
+ if (getLocalConfig(17)) fillRect (palegreen, 0, 7, 1, 2);
+
+ // Draw armed button
+ fillPixel(green, 14, 0);
+ }
+ drawKeyControl();
+ drawChordControl();
+ fillRect (orange, 14, scale * 2 + 1, 1, 2);
+
+}
+
+void drawKeyControl()
+{
+ int keyControl = transpose;
+ if (keyControl == -1) keyControl = 12;
+ else if (keyControl == -3) keyControl = 10;
+ else if (keyControl > 4) keyControl+=1;
+ fillRect (orange, keyControl, 0, 2, 1);
+}
+
+void drawChordControl()
+{
+ int chordControl = -2;
+ if (chord == 3) chordControl = 0;
+ else if (chord == 4) chordControl = 2;
+ else if (chord == 7) chordControl = 4;
+ else if (chord == 8) chordControl = 6;
+ else if (chord == 9) chordControl = 8;
+ else if (chord == 10) chordControl = 10;
+ else if (chord == 14) chordControl = 12;
+ fillRect (orange, chordControl, 14, 2, 1);
+}
+
+void executeTouchCommand(int touchIndex, int x, int y, float z, float vz)
+{
+ //drawNumber(x, 0xffff0000, 1, 1);
+ //drawNumber(y, 0xffff0000, 2, 9);
+
+ if (x == 193) {
+ if (y > 192) setLocalConfig(22, 6); // Blues
+ else if (y > 163) setLocalConfig(22, 5); // Pentatonic Minor
+ else if (y > 134) setLocalConfig(22, 4); // Pentatonic Major
+ else if (y > 106) setLocalConfig(22, 3); // Pentatonic Neutral
+ else if (y > 77) setLocalConfig(22, 2); // Harmonic Minor
+ else if (y > 34) setLocalConfig(22, 1); // Minor
+ else setLocalConfig(22, 0); // Major
+ }
+ else if (y == 193) { // CHORD:
+ if (x > 192) setLocalConfig(24, 1); // octave
+ else if (x > 172) setLocalConfig(24, 14); // m7b5
+ else if (x > 149) setLocalConfig(24, 10); // 9th
+ else if (x > 120) setLocalConfig(24, 9); // Minor 7th
+ else if (x > 91) setLocalConfig(24, 8); // Major 7th
+ else if (x > 63) setLocalConfig(24, 7); // 7th
+ else if (x > 34) setLocalConfig(24, 4); // Minor
+ else setLocalConfig(24, 3); // Major
+ if (chord == getLocalConfig(24)) setLocalConfig(24, 0);
+ drawNumber(getLocalConfig(24), 0xffff0000, 2, 2);
+ }
+ else if (y < 8) { // KEY TRANSPOSE:
+ if (x > 172) setLocalConfig(5, -1); // B
+ else if (x > 149) setLocalConfig(5, -3); // A
+ else if (x > 120) setLocalConfig(5, 7); // G
+ else if (x > 91) setLocalConfig(5, 5); // F
+ else if (x > 63) setLocalConfig(5, 4); // E
+ else if (x > 34) setLocalConfig(5, 2); // D
+ else setLocalConfig(5, 0); // C
+ }
+ else if (x == 7) {
+ if (y > 134) {}
+ else if (y > 106) {
+ setLocalConfig(17, int(!getLocalConfig(17))); // Piano Mode
+ } else if (y > 77) {
+ setLocalConfig(4, octave - 1);
+ drawNumber(getLocalConfig(4), 0xffff0000, 2, 2);
+ } else if (y > 34) {
+ setLocalConfig(4, octave + 1);
+ drawNumber(getLocalConfig(4), 0xffff0000, 2, 2);
+ }
+ else setLocalConfig(23, int(!hideMode)); // Major
+ }
+}
+
+bool drawGradient(int high, int low, int x, int y, int width, int height)
+{
+ int mid = blendARGB (high, low);
+
+ int dimColour = (dimFactor << 24);
+
+ high = blendARGB (high, dimColour);
+ mid = blendARGB (mid, dimColour);
+ low = blendARGB (low, dimColour);
+
+ blendGradientRect (high, mid, low, mid, x, y, width, height);
+
+ return true;
+}
+
+void drawPad (int x, int y, int colour, int bottomRightCornerDarkeningAmount)
+{
+ int dark = blendARGB (colour, bottomRightCornerDarkeningAmount << 24);
+ int mid = blendARGB (colour, (bottomRightCornerDarkeningAmount / 2) << 24);
+
+ int w = padWidth - padSpacing;
+ blendGradientRect (colour, mid, dark, mid, x * padWidth, y * padWidth, w, w);
+}
+
+//==============================================================================
+void initialiseScale()
+{
+ if (scale == 0) scaleBitmask = 0xab5; // major
+ else if (scale == 1) scaleBitmask = 0x5ad; // minor
+ else if (scale == 2) scaleBitmask = 0x9ad; // harmonic minor
+ else if (scale == 3) scaleBitmask = 0x4a5; // pentatonic neutral
+ else if (scale == 4) scaleBitmask = 0x295; // pentatonic major
+ else if (scale == 5) scaleBitmask = 0x4a9; // pentatomic minor
+ else if (scale == 6) scaleBitmask = 0x4e9; // blues
+ else if (scale == 7) scaleBitmask = 0x6ad; // dorian
+ else if (scale == 8) scaleBitmask = 0x5ab; // phrygian
+ else if (scale == 9) scaleBitmask = 0xad5; // lydian
+ else if (scale == 10) scaleBitmask = 0x6b5; // mixolydian
+ else if (scale == 11) scaleBitmask = 0x56b; // locrian
+ else if (scale == 12) scaleBitmask = 0x555; // whole tone
+ else if (scale == 13) scaleBitmask = 0xb6d; // arabic (A)
+ else if (scale == 14) scaleBitmask = 0x575; // arabic (B)
+ else if (scale == 15) scaleBitmask = 0x8d1; // japanese
+ else if (scale == 16) scaleBitmask = 0x8b1; // ryukyu
+ else if (scale == 17) scaleBitmask = 0x57b; // 8-tone spanish
+ else scaleBitmask = 0xfff; // chromatic
+
+ int n = scaleBitmask;
+ n -= ((n >> 1) & 0x5555);
+ n = (((n >> 2) & 0x3333) + (n & 0x3333));
+ n = (((n >> 4) + n) & 0x0f0f);
+ n += (n >> 8);
+ numNotesInScale = n & 0x3f;
+}
+
+//==============================================================================
+bool setDrumModePadColours()
+{
+ if (gridSize >= 4)
+ return false;
+
+ int numPads = gridSize * gridSize;
+
+ //for (int i = 0; i < numPads; ++i)
+ //{
+ // int note = getNoteForPad (i);
+ // Pad_setNote (i, note);
+ //}
+
+ if (gridSize == 1)
+ {
+ Pad_setColour (0, 0xff366CC5);
+ }
+ else
+ {
+ Pad_setColour (0, 0xffad64fb);
+ Pad_setColour (1, 0xff54e8fd);
+ Pad_setColour (2, 0xff3f89fb);
+ Pad_setColour (3, 0xff3c57fb);
+ }
+ return true;
+}
+
+//==============================================================================
+void initialisePads()
+{
+ setPitchCorrectionEnabled (gridSize >= 5);
+
+ padWidth = 15 / gridSize;
+ padSpacing = gridSize > 1 ? (15 - gridSize * padWidth) / (gridSize - 1) : 0;
+ padWidth += padSpacing;
+ dimFactor = 0;
+ dimDelay = 12;
+ activePads = 0;
+ int numPads = gridSize * gridSize;
+
+
+ if (setDrumModePadColours())
+ return;
+
+ for (int padIndex = 0; padIndex < numPads; ++padIndex)
+ {
+ // note numbers:
+ int note = getNoteForPad (padIndex);
+ if (note < 0) note = 0;
+
+ //Pad_setNote (padIndex, note);
+
+ // pad colours:
+ int padColour = 0xffffffff; // tonic = white
+
+ int noteInScale = mod (note - transpose, 12);
+
+ if (noteInScale != 0)
+ {
+ // not the tonic!
+
+ if (! hideMode && ! isPartOfScale (noteInScale))
+ {
+ padColour = 0xff000000;
+ }
+ else
+ {
+ int blend = 0xff * (noteInScale - 1) / 10;
+
+ padColour = blendARGB (gradientColour1 | 0xff000000,
+ (gradientColour2 & 0x00ffffff) | (blend << 24));
+ }
+ }
+
+ Pad_setColour (padIndex, padColour);
+
+ float padSize = (2.0 / float (gridSize));
+ float xStart = padSize * (padIndex % gridSize);
+ float yStart = padSize * (padIndex / gridSize);
+
+ addPitchCorrectionPad (padIndex, note, xStart, yStart, padSize, padSize);
+ }
+}
+
+//==============================================================================
+void initialiseTouches()
+{
+ for (int touchIndex = 0; touchIndex < 24; ++touchIndex)
+ {
+ Touch_setPad (touchIndex, 0xff);
+ Touch_setChannel (touchIndex, 0xff);
+ Touch_setTouchByHistory (0xff, touchIndex);
+ }
+}
+
+void initialiseChannels()
+{
+ for (int channel = 0; channel < 16; ++channel)
+ {
+ Channel_setTrackedTouch (channel, 0xff);
+ }
+}
+
+void initialiseConfig()
+{
+ clusterWidth = 1;
+ clusterHeight = 1;
+
+ setLocalConfigItemRange (4, -4, 6);
+ setLocalConfigItemRange (7, 0, 2);
+ setLocalConfigItemRange (20, 1, 7);
+ setLocalConfigItemRange (22, 0, 18);
+ setLocalConfigItemRange (24, 0, 14);
+
+ setLocalConfigItemRange (30, 1, 4);
+ setLocalConfigItemRange (31, 1, 4);
+
+ gridSize = getLocalConfig(20);
+ updateTopologyShift();
+
+ pitchbendRange = getLocalConfig(3);
+ octave = getLocalConfig(4);
+ scale = 0;
+ chord = 0;
+ hideMode = false;
+}
+
+void initialiseGlideLock()
+{
+ glideLockEnabled = false;
+ glideLockInitialNote = 0;
+ glideLockTarget = 0.0;
+ glideLockChannel = 0;
+}
+
+void initialise()
+{
+ setLocalConfigActiveState (0, true, true);
+ setLocalConfigActiveState (1, true, true);
+ setLocalConfigActiveState (2, true, true);
+ setLocalConfigActiveState (3, true, true);
+ setLocalConfigActiveState (4, true, true);
+ setLocalConfigActiveState (5, true, true);
+ setLocalConfigActiveState (6, true, true);
+ setLocalConfigActiveState (7, true, true);
+ setLocalConfigActiveState (10, true, true);
+ setLocalConfigActiveState (11, true, true);
+ setLocalConfigActiveState (12, true, true);
+ setLocalConfigActiveState (13, true, true);
+ setLocalConfigActiveState (14, true, true);
+ setLocalConfigActiveState (15, true, true);
+ setLocalConfigActiveState (16, true, true);
+ setLocalConfigActiveState (17, true, true);
+ setLocalConfigActiveState (18, true, true);
+ setLocalConfigActiveState (19, true, true);
+ setLocalConfigActiveState (20, true, true);
+ setLocalConfigActiveState (22, true, true);
+ setLocalConfigActiveState (23, true, true);
+ setLocalConfigActiveState (24, true, true);
+ setLocalConfigActiveState (30, true, true);
+ setLocalConfigActiveState (31, true, true);
+ setLocalConfigActiveState (32, true, true);
+
+ // Enable gamma correction if supported on hardware
+ setLocalConfig (33, 1);
+ gammaCorrected = getLocalConfig (33) > 0;
+
+ initialiseConfig();
+ initialiseScale();
+ initialisePads();
+ initialiseTouches();
+ initialiseChannels();
+ initialiseGlideLock();
+
+ useMPEDuplicateFilter (true);
+}
+
+void handleButtonDown (int index)
+{
+ if (index == 0)
+ {
+ ++gridSize;
+
+ if (gridSize == 3) gridSize = 4;
+ if (gridSize == 8) gridSize = 1;
+ resetGrid(gridSize);
+ }
+}
+
+void resetGrid(int gridSize)
+{
+ setLocalConfig (20, gridSize);
+
+ initialiseScale();
+ initialisePads ();
+ sendConfigItemToCluster (20);
+}
+
+//==============================================================================
+void repaint()
+{
+ clearDisplay();
+ updateDimFactor();
+
+ if (isConnectedToHost())
+ {
+ drawPads();
+ }
+
+ checkConfigUpdates();
+
+ // Overlay heatmap
+ drawPressureMap();
+ fadePressureMap();
+}
+
+//==============================================================================
+int getAbsPitch (int touchIndex, float x)
+{
+ float deltaX = (x - 1.0) * 12.0;
+ return getPitchWheelFromDeltaX (deltaX);
+}
+
+int getPitchwheelValue (int touchIndex, float x)
+{
+ float initialX = Touch_getInitialX (touchIndex);
+ float scaler = (1.0 / (2.1 / float (gridSize)));
+ float deltaX = transformPitchForHideMode (touchIndex, scaler * (x - initialX));
+ int correction = 0;
+
+ if (gridSize >= 5)
+ correction = getPitchCorrectionPitchBend (touchIndex, getPitchWheelFromDeltaX (deltaX) - 8192);
+
+ return getPitchWheelFromDeltaX (deltaX) + correction;
+}
+
+int getPitchWheelFromDeltaX (float deltaX)
+{
+ deltaX = clamp (float (-getLocalConfig (3)), float (getLocalConfig (3)), deltaX);
+
+ // now convert pitchbend in semitones to 14-bit pitchwheel position:
+ float pitchwheel = deltaX > 0.0
+ ? map (deltaX, 0.0, float (getLocalConfig(3)), 8192.0, 16383.0)
+ : map (deltaX, float (-getLocalConfig(3)), 0.0, 0.0, 8192.0);
+
+ return clamp (0, 16383, int (pitchwheel));
+}
+
+float transformPitchForHideMode (int touchIndex, float deltaX)
+{
+ if (! hideMode)
+ return deltaX;
+
+ // interpolate between actual pitches of pads left and right to x
+
+ int deltaXLeft = deltaX < 0 ? int (deltaX) - 1 : int (deltaX);
+ int initialPadIndex = Touch_getPad (touchIndex);
+
+ int padIndexLeft = deltaXLeft + initialPadIndex;
+ int padIndexRight = padIndexLeft + 1;
+
+ // rows are incrementing when going down, not up!
+ // if padIndexLeft/Right is outside of the edges of the block, you need
+ // to explicitly add/subtract two rows to compensate.
+ if (mod (padIndexLeft, gridSize) == gridSize - 1)
+ {
+ if (deltaX < 0)
+ padIndexLeft += 2 * gridSize;
+
+ else if (deltaX > 0)
+ padIndexRight -= 2 * gridSize;
+ }
+
+ float pitchLeft = getNoteForPad (padIndexLeft);
+ float pitchRight = getNoteForPad (padIndexRight);
+
+ float deltaPitch = deltaX - float (deltaXLeft);
+ float pitch = (pitchLeft * (1 - deltaPitch)) + (pitchRight * deltaPitch);
+
+
+ //return pitch - float (Pad_getNote (initialPadIndex));
+ return pitch - float (getNoteForPad (initialPadIndex));
+}
+
+//==============================================================================
+int getYAxisValue (int touchIndex, float y)
+{
+ if (getLocalConfig (7) == 0)
+ return clamp (0, 127, int (127 - int (y * 63.5)));
+
+ if (getLocalConfig (7) == 1)
+ return getYAxisBipolar (touchIndex, y);
+
+ float yDelta = Touch_getInitialY (touchIndex) - y;
+
+ y = 0.5 + (applyCurve (yDelta * 0.5));
+
+ return clamp (0, 127, int (y * 127));
+}
+
+int getYAxisBipolar (int touchIndex, float y)
+{
+ float yDelta = abs (y - Touch_getInitialY (touchIndex));
+
+ y = applyCurve (yDelta * 0.5);
+
+ return clamp (0, 127, int (y * 127));
+}
+
+// Faster with lower value
+float applyCurve (float yDelta)
+{
+ float scaler = float (getLocalConfig (12)) / 127.0;
+
+ if (scaler > 0.0)
+ return yDelta / scaler;
+ else
+ return yDelta;
+}
+
+//==============================================================================
+void addTouchToList (int touchIndex)
+{
+ int endOfList = 0;
+
+ while ((endOfList < 24) && (Touch_getTouchByHistory (endOfList) != 0xff))
+ ++endOfList;
+
+ if (endOfList < 24)
+ Touch_setTouchByHistory (touchIndex, endOfList);
+}
+
+void deleteFromTouchList (int indexToDelete)
+{
+ for (int i = indexToDelete; i < 23; ++i)
+ {
+ if (Touch_getTouchByHistory (i) == 0xff)
+ return;
+
+ Touch_setTouchByHistory (Touch_getTouchByHistory (i + 1), i);
+ }
+
+ Touch_setTouchByHistory (0xff, 23);
+}
+
+void removeTouchFromList (int touchIndex)
+{
+ for (int i = 0; i < 24; ++i)
+ {
+ int touch = Touch_getTouchByHistory (i);
+
+ if (touch == 0xff)
+ return;
+
+ if (touch == touchIndex)
+ {
+ deleteFromTouchList (i);
+ return;
+ }
+ }
+}
+
+int getNumTouchesInList()
+{
+ int indexInList = 0;
+
+ while (Touch_getTouchByHistory (indexInList) != 0xff && indexInList < 24)
+ ++indexInList;
+
+ return indexInList;
+}
+
+//==============================================================================
+void resetGlideLockToNote (int note, int channel)
+{
+ glideLockInitialNote = note;
+ glideLockChannel = channel;
+ glideLockTarget = 8192.0;
+}
+
+int getGlideLockRate()
+{
+ return int (map (float (getLocalConfig (18)), 0.0, 127.0, 16.0, 3000.0));
+}
+
+void setGlideLockTarget (int note)
+{
+ float delta = float (note - glideLockInitialNote);
+ glideLockTarget = getPitchWheelFromDeltaX (delta);
+ sendPitchBend (glideLockChannel, int (glideLockTarget), getGlideLockRate());
+}
+
+int getGlideLockDelta()
+{
+ return glideLockEnabled
+ ? int (map (glideLockTarget, 0.0, 16383.0, -8191.0, 8192.0))
+ : 0;
+}
+
+//==============================================================================
+
+void touchStart (int touchIndex, float x, float y, float z, float vz)
+{
+ int X = int (x*100);
+ int Y = int (y*100);
+
+ if (Y < 8 && X > 192)
+ armTouchCommands = !armTouchCommands;
+
+ else if (armTouchCommands && (X == 193 || X == 7 || Y == 7 || Y == 193))
+ executeTouchCommand(touchIndex, X, Y, z, vz);
+
+ else if (Touch_getPad (touchIndex) == 0xff)
+ playNote(touchIndex, x, y, z, vz);
+}
+
+void playNote (int touchIndex, float x, float y, float z, float vz)
+{
+
+ int padIndex = getTouchedPad (x, y);
+ //int note = clamp (0, 127, Pad_getNote (padIndex));
+ int note = clamp (0, 127, getNoteForPad (padIndex));
+ int colour = Pad_getColour (padIndex);
+ int channel = 0xff;
+ int velocity = clamp (1, 127, int (vz * 127.0));
+ int pressure = clamp (0, 127, int (z * 127.0));
+ bool glideLockConfigEnabled = getLocalConfig (19);
+ bool enableMidiNoteOn = true;
+
+ addTouchToList (touchIndex);
+
+ if (glideLockEnabled || (glideLockConfigEnabled && (gridSize > 1)))
+ {
+ if (! glideLockEnabled)
+ {
+ glideLockEnabled = true;
+ channel = assignChannel (note);
+ resetGlideLockToNote (note, channel);
+ }
+ else
+ {
+ channel = glideLockChannel;
+ setGlideLockTarget (note);
+ enableMidiNoteOn = false;
+ }
+ }
+
+ if (channel == 0xff)
+ channel = assignChannel (note);
+
+ Touch_setInitialY (touchIndex, y);
+
+ if (enableMidiNoteOn)
+ {
+ if (pitchbendRange > 0)
+ {
+ if (gridSize == 1)
+ sendPitchBend (channel, getAbsPitch (touchIndex, x));
+ else
+ sendPitchBend (channel, 8192);
+ }
+
+ if (getLocalConfig (12))
+ sendMIDI (0xb0 | channel, getLocalConfig (6), getYAxisValue (touchIndex, y));
+
+ sendMIDI (0xd0 | channel, pressure);
+
+ sendNoteOn (channel, note, velocity);
+ }
+
+ addPressurePoint (getTrailColour (colour), x, y, z * 32.0);
+
+ Pad_setActive (padIndex, true);
+
+ Touch_setPad (touchIndex, padIndex);
+ Touch_setNote (touchIndex, note);
+ Touch_setInitialX (touchIndex, x);
+ Touch_setChannel (touchIndex, channel);
+ Touch_setVelocity (touchIndex, velocity);
+
+ Channel_setTrackedTouch (channel, touchIndex);
+}
+
+void touchMove (int touchIndex, float x, float y, float z, float vz)
+{
+
+ int padIndex = Touch_getPad (touchIndex);
+
+ if (padIndex == 0xff)
+ return; // touch was not started.
+
+ int channel = Touch_getChannel (touchIndex);
+
+ if (Channel_getTrackedTouch (channel) != touchIndex)
+ return; // these are not the touch messages you're looking for...
+
+ int note = Touch_getNote (touchIndex);
+ int pressure = clamp (0, 127, int (z * 127.0));
+
+ sendMIDI (0xd0 | channel, pressure);
+
+ // Piano Mode acts as a fret
+ if (getLocalConfig (17))
+ {
+ int newPadIndex = getTouchedPad (x, y);
+ //int newNote = clamp (0, 127, Pad_getNote (newPadIndex));
+ int newNote = clamp (0, 127, getNoteForPad (newPadIndex));
+
+ if (note != newNote)
+ {
+ if (! glideLockEnabled)
+ {
+ sendNoteOff (channel, note, 0);
+ sendNoteOn (channel, newNote, Touch_getVelocity (touchIndex));
+ }
+ else
+ {
+ setGlideLockTarget (newNote);
+ }
+
+ Touch_setNote (touchIndex, newNote);
+ Touch_setPad (touchIndex, newPadIndex);
+ Pad_setActive (padIndex, false);
+ Pad_setActive (newPadIndex, true);
+ }
+ }
+ else
+ {
+ if (getLocalConfig (12))
+ sendMIDI (0xb0 | channel, getLocalConfig (6), getYAxisValue (touchIndex, y));
+
+ if (pitchbendRange > 0)
+ {
+ int pitchwheelValue = 0;
+
+ if (gridSize == 1)
+ pitchwheelValue = getAbsPitch (touchIndex, x);
+ else if (glideLockEnabled)
+ pitchwheelValue = getPitchwheelValue (touchIndex, x) + getGlideLockDelta();
+ else
+ pitchwheelValue = getPitchwheelValue (touchIndex, x);
+
+
+ sendPitchBend (channel, clamp (0, 16383, pitchwheelValue));
+ }
+ }
+
+ addPressurePoint (getTrailColour (Pad_getColour (padIndex)), x, y, z * 32.0);
+}
+
+void touchEnd (int touchIndex, float x, float y, float z, float vz)
+{
+ int padIndex = Touch_getPad (touchIndex);
+
+ if (padIndex == 0xff)
+ return; // touch was not started.
+
+ int channel = Touch_getChannel (touchIndex);
+
+ int note = Touch_getNote (touchIndex);
+ int velocity = clamp (0, 127, int (vz * 127.0));
+
+ if (glideLockEnabled)
+ {
+ int numEvents = getNumTouchesInList();
+ int eventNum = numEvents - 1;
+
+ while (Touch_getTouchByHistory (eventNum) != touchIndex)
+ eventNum--;
+
+ if (numEvents == 1)
+ {
+ glideLockEnabled = false;
+ sendPitchBend (glideLockChannel, 8192, 0);
+ sendNoteOff (glideLockChannel, glideLockInitialNote, velocity);
+ Channel_setTrackedTouch (glideLockChannel, 0xff);
+ deassignChannel (glideLockInitialNote, glideLockChannel);
+ }
+ else if (eventNum == (numEvents - 1))
+ {
+ int previousTouch = Touch_getTouchByHistory (eventNum - 1);
+ int previousNote = Touch_getNote (previousTouch);
+
+ setGlideLockTarget (previousNote);
+ Channel_setTrackedTouch (glideLockChannel, previousTouch);
+ Pad_setActive (Touch_getPad (previousTouch), true);
+ }
+ }
+ else
+ {
+ sendNoteOff (channel, note, velocity);
+ Channel_setTrackedTouch (channel, 0xff);
+ deassignChannel (note, channel);
+ }
+
+ Pad_setActive (padIndex, false);
+
+ Touch_setPad (touchIndex, 0xff);
+ Touch_setChannel (touchIndex, 0xff);
+
+ removeTouchFromList (touchIndex);
+}
+
+void updateTopologyShift ()
+{
+ int xShiftLast = xShift;
+ int yShiftLast = yShift;
+ xShift = 0;
+ yShift = 0;
+
+
+ if (getClusterWidth() > 1)
+ {
+ if (gridSize < 5)
+ {
+ xShift = getClusterXpos();
+ }
+ else if (isMasterInCurrentCluster())
+ {
+ xShift = getHorizontalDistFromMaster() / 2;
+ }
+ else
+ {
+ int octStart = ((getClusterWidth() - 1) / 2);
+ xShift = (getClusterXpos() - octStart);
+ }
+ }
+
+ if (getClusterHeight() > 1)
+ {
+ if (gridSize < 5)
+ {
+ yShift = getClusterYpos();
+ }
+ if (isMasterInCurrentCluster())
+ {
+ yShift = getVerticalDistFromMaster() / 2;
+ }
+ else
+ {
+ int octStart = ((getClusterHeight() - 1) / 2);
+ yShift = (getClusterYpos() - octStart);
+ }
+ }
+
+ if (clusterWidth != getClusterWidth() || xPos != getClusterXpos() || xShiftLast != xShift)
+ {
+ if (isMasterInCurrentCluster())
+ {
+ if (isMasterBlock())
+ syncCluster();
+ }
+ else if (! getClusterXpos())
+ syncCluster();
+
+ initialiseScale();
+ initialisePads();
+
+ clusterWidth = getClusterWidth();
+ xPos = getClusterXpos();
+ }
+ else if (clusterHeight != getClusterHeight() || yPos != getClusterYpos() || yShiftLast != yShift)
+ {
+ if (isMasterInCurrentCluster())
+ {
+ if (isMasterBlock())
+ syncCluster();
+ }
+ else if (! getClusterYpos())
+ syncCluster();
+
+ initialiseScale();
+ initialisePads();
+
+ clusterHeight = getClusterHeight();
+ yPos = getClusterYpos();
+ }
+}
+
+void syncCluster()
+{
+ if (getNumBlocksInCurrentCluster() < 2)
+ return;
+
+ for (int i = 4; i <= 7; ++i)
+ sendConfigItemToCluster (i);
+
+ for (int i = 10; i <= 23; ++i)
+ sendConfigItemToCluster (i);
+}
+
+void sendConfigItemToCluster (int itemId)
+{
+ if (getNumBlocksInCurrentCluster() < 2)
+ return;
+
+ int numBlocksInCluster = getNumBlocksInCurrentCluster();
+
+ for (int i = 0; i < numBlocksInCluster; ++i)
+ if (getBlockIdForBlockInCluster(i) != getBlockIDForIndex(0))
+ setRemoteConfig (getBlockIdForBlockInCluster(i), itemId, getLocalConfig (itemId));
+}
+
+void checkConfigUpdates ()
+{
+ if (scale != getLocalConfig (22))
+ {
+ scale = getLocalConfig (22);
+ initialiseScale();
+ initialisePads();
+ }
+ if (chord != getLocalConfig (24))
+ {
+ chord = getLocalConfig (24);
+ initialiseScale();
+ initialisePads();
+ }
+ if (gridSize != getLocalConfig (20))
+ {
+ gridSize = getLocalConfig (20);
+ initialiseScale();
+ initialisePads();
+ }
+ if (octave != getLocalConfig (4))
+ {
+ octave = getLocalConfig (4);
+ initialisePads();
+ }
+ if (hideMode != getLocalConfig (23))
+ {
+ hideMode = getLocalConfig (23);
+ initialiseScale();
+ initialisePads();
+ }
+ if (transpose != getLocalConfig (5))
+ {
+ transpose = getLocalConfig (5);
+ initialisePads();
+ }
+
+ updateTopologyShift();
+}
+
+// Macros are 0-127
+int macro0Level;
+int macro1Level;
+int macro2Level;
+int padXpos;
+int padYpos;
+int vSens;
+int xSens;
+int ySens;
+int zSens;
+int lSens;
+int lastReqTime;
+
+void requestSensFromBlock (int address)
+{
+ if (getMillisecondCounter() > lastReqTime + 120)
+ {
+ sendMessageToBlock (address, 0x783C53D5, getBlockIDForIndex (0), 0);
+ lastReqTime = getMillisecondCounter();
+ }
+}
+
+
+void handleMessage (int data0, int data1, int data2)
+{
+ // Sensitivities Response
+ if (data0 == 0x783E53D5)
+ {
+ vSens = (data1 >> 24) & 0x7F;
+ xSens = (data1 >> 16) & 0x7F;
+ ySens = (data1 >> 8) & 0x7F;
+ zSens = (data1 >> 0) & 0x7F;
+ lSens = (data2 >> 24) & 0x7F;
+ }
+}
+
+const float padLowX = 0.12;
+const float padHighX = 1.88;
+const float padLowY = 0.12;
+const float padHighY = 1.5;
+
+void handleXYTouch (int touchIndex, float x, float y, float z, bool newTouch)
+{
+ if (touchIndex > 1)
+ return;
+
+ int newPadXpos = int (map (clamp (padLowX, padHighX, x), padLowX, padHighX, 0.0, 127.0));
+ int newPadYpos = 127 - int (map (clamp (padLowY, padHighY, y), padLowY, padHighY, 0.0, 127.0));
+
+ if (padXpos != newPadXpos)
+ {
+ padXpos = newPadXpos;
+ sendCC (getControlChannel(), 113, padXpos);
+ }
+ if (padYpos != newPadYpos)
+ {
+ padYpos = newPadYpos;
+ sendCC (getControlChannel(), 114, padYpos);
+ }
+}
+
+void drawXYPad()
+{
+ blendGradientRect (0xFF40C0B0, 0xff88f843, 0xFF40C0B0, 0xFF003060, 0, 0, 15, 12);
+ blendCircle (0xFFFFFFFF, map (float (padXpos), 0.0, 127.0, 0.0, 14.0), map (float (127 - padYpos), 0.0, 127.0, 0.0, 12.0), 1.4, true);
+}
+
+void handleMIDI (int byte0, int byte1, int byte2)
+{
+ if ((byte0 & 0xF0) == 0xB0)
+ {
+ if (byte1 == 107) macro0Level = byte2;
+ if (byte1 == 109) macro1Level = byte2;
+ if (byte1 == 111) macro2Level = byte2;
+ if (byte1 == 113) padXpos = byte2;
+ if (byte1 == 114) padYpos = byte2;
+ }
+}
+
+
+/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+*/
diff --git a/Factory Scripts/BlockBOARD.littlefootModes/MPE.mode b/Factory Scripts/BlockBOARD.littlefootModes/MPE.mode
new file mode 100644
index 0000000..1829968
--- /dev/null
+++ b/Factory Scripts/BlockBOARD.littlefootModes/MPE.mode
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Factory Scripts/BlockBOARD.littlefootModes/Multi Channel.mode b/Factory Scripts/BlockBOARD.littlefootModes/Multi Channel.mode
new file mode 100644
index 0000000..cda8888
--- /dev/null
+++ b/Factory Scripts/BlockBOARD.littlefootModes/Multi Channel.mode
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Factory Scripts/BlockBOARD.littlefootModes/Single Channel.mode b/Factory Scripts/BlockBOARD.littlefootModes/Single Channel.mode
new file mode 100644
index 0000000..eb1a9b6
--- /dev/null
+++ b/Factory Scripts/BlockBOARD.littlefootModes/Single Channel.mode
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+