Firmware for HexBoard MIDI controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// class definitions are in a header so that
// they get read before compiling the main program.

class tuningDef {
public:
  std::string name; // limit is 17 characters for GEM menu
  byte cycleLength; // steps before period/cycle/octave repeats
  float stepSize;   // in cents, 100 = "normal" semitone.
  SelectOptionInt keyChoices[MAX_SCALE_DIVISIONS];
  int spanCtoA() {
    return (- keyChoices[0].val_int);
  };
};

class layoutDef {
public:
  std::string name;    // limit is 17 characters for GEM menu
  bool isPortrait;     // affects orientation of the GEM menu only.
  byte hexMiddleC;     // instead of "what note is button 1", "what button is the middle"
  int8_t acrossSteps;  // defined this way to be compatible with original v1.1 firmare
  int8_t dnLeftSteps;  // defined this way to be compatible with original v1.1 firmare
  byte tuning;         // index of the tuning that this layout is designed for
};

class colorDef {
public:
  float hue;
  byte sat;
  byte val;
  colorDef mixWithWhite() {
    colorDef temp;
    temp.hue = this->hue;
    temp.sat = ((this->sat > SAT_TINT) ? SAT_TINT : this->sat);
    temp.val = VALUE_FULL;
    return temp;
  };
};

class paletteDef {
public:
  colorDef swatch[MAX_SCALE_DIVISIONS]; // the different colors used in this palette
  byte colorNum[MAX_SCALE_DIVISIONS];   // map key (c,d...) to swatches
  colorDef getColor(byte givenStepFromC) {
    return swatch[colorNum[givenStepFromC] - 1];
  };
  float getHue(byte givenStepFromC) {
    return getColor(givenStepFromC).hue;
  };
  byte getSat(byte givenStepFromC) {
    return getColor(givenStepFromC).sat;
  };
  byte getVal(byte givenStepFromC) {
    return getColor(givenStepFromC).val;
  };
};

class buttonDef {
public:
  byte     btnState = 0;        // binary 00 = off, 01 = just pressed, 10 = just released, 11 = held
  void interpBtnPress(bool isPress) {
    btnState = (((btnState << 1) + isPress) & 3);
  };
  int8_t   coordRow = 0;        // hex coordinates
  int8_t   coordCol = 0;        // hex coordinates
  uint32_t timePressed = 0;     // timecode of last press
  uint32_t LEDcolorAnim = 0;    // calculate it once and store value, to make LED playback snappier 
  uint32_t LEDcolorPlay = 0;    // calculate it once and store value, to make LED playback snappier
  uint32_t LEDcolorOn = 0;      // calculate it once and store value, to make LED playback snappier
  uint32_t LEDcolorOff = 0;     // calculate it once and store value, to make LED playback snappier
  bool     animate = 0;         // hex is flagged as part of the animation in this frame, helps make animations smoother
  int16_t  stepsFromC = 0;      // number of steps from C4 (semitones in 12EDO; microtones if >12EDO)
  bool     isCmd = 0;           // 0 if it's a MIDI note; 1 if it's a MIDI control cmd
  bool     inScale = 0;         // 0 if it's not in the selected scale; 1 if it is
  byte     note = UNUSED_NOTE;  // MIDI note or control parameter corresponding to this hex
  int16_t  bend = 0;            // in microtonal mode, the pitch bend for this note needed to be tuned correctly
  byte     channel = 0;         // what MIDI channel this note is playing on
  float    frequency = 0.0;     // what frequency to ring on the buzzer
};

class wheelDef {
public:
  bool alternateMode; // two ways to control
  bool isSticky;      // TRUE if you leave value unchanged when no buttons pressed
  byte* topBtn;       // pointer to the key Status of the button you use as this button
  byte* midBtn;
  byte* botBtn;
  int16_t minValue;
  int16_t maxValue;
  int* stepValue; // this can be changed via GEM menu
  int16_t defValue; // snapback value
  int16_t curValue;
  int16_t targetValue;
  uint32_t timeLastChanged;
  void setTargetValue() {
    if (alternateMode) {
      if (*midBtn >> 1) { // middle button toggles target (0) vs. step (1) mode
        int16_t temp = curValue;
            if (*topBtn == 1)     {temp += *stepValue;}; // tap button
            if (*botBtn == 1)     {temp -= *stepValue;}; // tap button
            if (temp > maxValue)  {temp  = maxValue;} 
        else if (temp <= minValue) {temp  = minValue;};
        targetValue = temp;
      } else {
        switch (((*topBtn >> 1) << 1) + (*botBtn >> 1)) {
          case 0b10:   targetValue = maxValue;     break;
          case 0b11:   targetValue = defValue;     break;
          case 0b01:   targetValue = minValue;     break;
          default:     targetValue = curValue;     break;
        };
      };
    } else {
      switch (((*topBtn >> 1) << 2) + ((*midBtn >> 1) << 1) + (*botBtn >> 1)) {
        case 0b100:  targetValue = maxValue;                         break;
        case 0b110:  targetValue = (3 * maxValue + minValue) / 4;    break;
        case 0b010:
        case 0b111:
        case 0b101:  targetValue = (maxValue + minValue) / 2;        break;
        case 0b011:  targetValue = (maxValue + 3 * minValue) / 4;    break;
        case 0b001:  targetValue = minValue;                         break;
        case 0b000:  targetValue = (isSticky ? curValue : defValue); break;
        default: break;
      };
    }
  };
  bool updateValue(uint32_t givenTime) {
    int16_t temp = targetValue - curValue;
    if (temp != 0) {
      if ((givenTime - timeLastChanged) >= CC_MSG_COOLDOWN_MICROSECONDS ) {
        timeLastChanged = givenTime;
        if (abs(temp) < *stepValue) {
          curValue = targetValue;
        } else {
          curValue = curValue + (*stepValue * (temp / abs(temp)));
        };
        return 1;
      } else {
        return 0;
      };
    } else {
      return 0;
    };
  };   
};
// back button

class scaleDef {
public:
  std::string name;
  byte tuning;
  byte pattern[MAX_SCALE_DIVISIONS];
};

// this class should only be touched by the 2nd core
class oscillator {
public:
  uint16_t increment = 0;
  uint16_t counter = 0;
};