#include "MIDIUSB.h" // see https://github.com/arduino-libraries/MIDIUSB
#include <math.h>

#define DEBUG
#undef DEBUG

#define TUNE
#undef TUNE

#define MIDI_IN_PIN 0

#define OVERALL_VELOCITY_PIN 10

#define VALVE_0 4 // PD4 // 6mm
#define VALVE_1 5 // PC6 // 6mm
#define VALVE_2 6 // PD7 // 6mm
#define VALVE_3 7 // PE6 // 4mm
#define VALVE_4 8 // PB4 // 4mm
#define VALVE_5 9 // PB5 // 4mm
#define VALVE_6 3 // PD0 // 4mm
#define VALVE_7 2 // PD1 // 4mm

#define NUM_VALVES 8

// array of currently active notes, index 0 is the oldest one
static int16_t notes[NUM_VALVES] = {-1, -1, -1, -1, -1, -1, -1, -1};

// array of the length of the tone period required to create the tone
static uint16_t periods[NUM_VALVES] = {0, 0, 0, 0, 0, 0, 0, 0};

// array of the current state of the valve pins (for tone periods)
static uint8_t states[NUM_VALVES] = {0, 0, 0, 0, 0, 0, 0, 0};

// array for the period timer helpers
static uint64_t lastNoteMicros[NUM_VALVES] = {0, 0, 0, 0, 0, 0, 0, 0};

// array of the valve pin numbers
static const uint8_t valves[NUM_VALVES] = {VALVE_0, VALVE_1, VALVE_2, VALVE_3, VALVE_4, VALVE_5, VALVE_6, VALVE_7};

static float noteToMidi[128];

static uint16_t noteToPeriod[128];
static uint16_t noteToPeriodDefault[128];

#ifdef TUNE
uint8_t lastNote = 0;
#endif

bool enableTuning = true;

// MIDI parameter overview
// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void setup() {
  #ifdef DEBUG
    Serial.begin(115200);
    Serial.println("Debug activated");
  #endif

  #ifdef TUNE
    Serial.begin(115200);
    Serial.println("Tuning activated");
  #endif

  Serial1.begin(31250); // MIDI baud rate

  // initialize valve output pins
  for (uint8_t i = 0; i < NUM_VALVES; i++) {
    pinMode(valves[i], OUTPUT);
  }

  pinMode(OVERALL_VELOCITY_PIN, OUTPUT);

  noteToMidi[127] = 12543.85;
  noteToPeriod[127] = 40;
  noteToPeriodDefault[127] = 40;

  noteToMidi[126] = 11839.82;
  noteToPeriod[126] = 42;
  noteToPeriodDefault[126] = 42;

  noteToMidi[125] = 11175.30;
  noteToPeriod[125] = 45;
  noteToPeriodDefault[125] = 45;

  noteToMidi[124] = 10548.08;
  noteToPeriod[124] = 47;
  noteToPeriodDefault[124] = 47;

  noteToMidi[123] = 9956.06;
  noteToPeriod[123] = 50;
  noteToPeriodDefault[123] = 50;

  noteToMidi[122] = 9397.27;
  noteToPeriod[122] = 53;
  noteToPeriodDefault[122] = 53;

  noteToMidi[121] = 8869.84;
  noteToPeriod[121] = 56;
  noteToPeriodDefault[121] = 56;

  noteToMidi[120] = 8372.02;
  noteToPeriod[120] = 60;
  noteToPeriodDefault[120] = 60;

  noteToMidi[119] = 7902.13;
  noteToPeriod[119] = 63;
  noteToPeriodDefault[119] = 63;

  noteToMidi[118] = 7458.62;
  noteToPeriod[118] = 67;
  noteToPeriodDefault[118] = 67;

  noteToMidi[117] = 7040.00;
  noteToPeriod[117] = 71;
  noteToPeriodDefault[117] = 71;

  noteToMidi[116] = 6644.88;
  noteToPeriod[116] = 75;
  noteToPeriodDefault[116] = 75;

  noteToMidi[115] = 6271.93;
  noteToPeriod[115] = 80;
  noteToPeriodDefault[115] = 80;

  noteToMidi[114] = 5919.91;
  noteToPeriod[114] = 84;
  noteToPeriodDefault[114] = 84;

  noteToMidi[113] = 5587.65;
  noteToPeriod[113] = 89;
  noteToPeriodDefault[113] = 89;

  noteToMidi[112] = 5274.04;
  noteToPeriod[112] = 95;
  noteToPeriodDefault[112] = 95;

  noteToMidi[111] = 4978.03;
  noteToPeriod[111] = 100;
  noteToPeriodDefault[111] = 100;

  noteToMidi[110] = 4698.64;
  noteToPeriod[110] = 106;
  noteToPeriodDefault[110] = 106;

  noteToMidi[109] = 4434.92;
  noteToPeriod[109] = 113;
  noteToPeriodDefault[109] = 113;

  noteToMidi[108] = 4186.01;
  noteToPeriod[108] = 119;
  noteToPeriodDefault[108] = 119;

  noteToMidi[107] = 3951.07;
  noteToPeriod[107] = 127;
  noteToPeriodDefault[107] = 127;

  noteToMidi[106] = 3729.31;
  noteToPeriod[106] = 134;
  noteToPeriodDefault[106] = 134;

  noteToMidi[105] = 3520.00;
  noteToPeriod[105] = 142;
  noteToPeriodDefault[105] = 142;

  noteToMidi[104] = 3322.44;
  noteToPeriod[104] = 150;
  noteToPeriodDefault[104] = 150;

  noteToMidi[103] = 3135.96;
  noteToPeriod[103] = 159;
  noteToPeriodDefault[103] = 159;

  noteToMidi[102] = 2959.96;
  noteToPeriod[102] = 169;
  noteToPeriodDefault[102] = 169;

  noteToMidi[101] = 2793.83;
  noteToPeriod[101] = 179;
  noteToPeriodDefault[101] = 179;

  noteToMidi[100] = 2637.02;
  noteToPeriod[100] = 190;
  noteToPeriodDefault[100] = 190;

  noteToMidi[99] = 2489.02;
  noteToPeriod[99] = 201;
  noteToPeriodDefault[99] = 201;

  noteToMidi[98] = 2349.32;
  noteToPeriod[98] = 213;
  noteToPeriodDefault[98] = 213;

  noteToMidi[97] = 2217.46;
  noteToPeriod[97] = 225;
  noteToPeriodDefault[97] = 225;

  noteToMidi[96] = 2093.00;
  noteToPeriod[96] = 239;
  noteToPeriodDefault[96] = 239;

  noteToMidi[95] = 1975.53;
  noteToPeriod[95] = 253;
  noteToPeriodDefault[95] = 253;

  noteToMidi[94] = 1864.66;
  noteToPeriod[94] = 268;
  noteToPeriodDefault[94] = 268;

  noteToMidi[93] = 1760.00;
  noteToPeriod[93] = 284;
  noteToPeriodDefault[93] = 284;

  noteToMidi[92] = 1661.22;
  noteToPeriod[92] = 301;
  noteToPeriodDefault[92] = 301;

  noteToMidi[91] = 1567.98;
  noteToPeriod[91] = 319;
  noteToPeriodDefault[91] = 319;

  noteToMidi[90] = 1479.98;
  noteToPeriod[90] = 338;
  noteToPeriodDefault[90] = 338;

  noteToMidi[89] = 1396.91;
  noteToPeriod[89] = 358;
  noteToPeriodDefault[89] = 358;

  noteToMidi[88] = 1318.51;
  noteToPeriod[88] = 379;
  noteToPeriodDefault[88] = 379;

  noteToMidi[87] = 1244.51;
  noteToPeriod[87] = 402;
  noteToPeriodDefault[87] = 402;

  noteToMidi[86] = 1174.66;
  noteToPeriod[86] = 426;
  noteToPeriodDefault[86] = 426;

  noteToMidi[85] = 1108.73;
  noteToPeriod[85] = 451;
  noteToPeriodDefault[85] = 451;

  noteToMidi[84] = 1046.50;
  noteToPeriod[84] = 478;
  noteToPeriodDefault[84] = 478;

  noteToMidi[83] = 987.77;
  noteToPeriod[83] = 506;
  noteToPeriodDefault[83] = 506;

  noteToMidi[82] = 932.33;
  noteToPeriod[82] = 536;
  noteToPeriodDefault[82] = 536;

  noteToMidi[81] = 880.00;
  noteToPeriod[81] = 568;
  noteToPeriodDefault[81] = 568;

  noteToMidi[80] = 830.61;
  noteToPeriod[80] = 602;
  noteToPeriodDefault[80] = 602;

  noteToMidi[79] = 783.99;
  noteToPeriod[79] = 638;
  noteToPeriodDefault[79] = 638;

  noteToMidi[78] = 739.99;
  noteToPeriod[78] = 676;
  noteToPeriodDefault[78] = 676;

  noteToMidi[77] = 698.46;
  noteToPeriod[77] = 716;
  noteToPeriodDefault[77] = 716;

  noteToMidi[76] = 659.26;
  noteToPeriod[76] = 758;
  noteToPeriodDefault[76] = 758;

  noteToMidi[75] = 622.25;
  noteToPeriod[75] = 804;
  noteToPeriodDefault[75] = 804;

  noteToMidi[74] = 587.33;
  noteToPeriod[74] = 851;
  noteToPeriodDefault[74] = 851;

  noteToMidi[73] = 554.37;
  noteToPeriod[73] = 902;
  noteToPeriodDefault[73] = 902;

  noteToMidi[72] = 523.25;
  noteToPeriod[72] = 956;
  noteToPeriodDefault[72] = 956;

  noteToMidi[71] = 493.88;
  noteToPeriod[71] = 995; // 995
  noteToPeriodDefault[71] = 1012;

  noteToMidi[70] = 466.16;
  noteToPeriod[70] = 1073;
  noteToPeriodDefault[70] = 1073;

  noteToMidi[69] = 440.00;
  noteToPeriod[69] = 1136;
  noteToPeriodDefault[69] = 1136;

  noteToMidi[68] = 415.30;
  noteToPeriod[68] = 1204;
  noteToPeriodDefault[68] = 1204;

  noteToMidi[67] = 392.00;
  noteToPeriod[67] = 1276;
  noteToPeriodDefault[67] = 1276;

  noteToMidi[66] = 369.99;
  noteToPeriod[66] = 1351;
  noteToPeriodDefault[66] = 1351;

  noteToMidi[65] = 349.23;
  noteToPeriod[65] = 1403; // 1403
  noteToPeriodDefault[65] = 1432;

  noteToMidi[64] = 329.63;
  noteToPeriod[64] = 1480; // 1480
  noteToPeriodDefault[64] = 1517;

  noteToMidi[63] = 311.13;
  noteToPeriod[63] = 1560; // 1560
  noteToPeriodDefault[63] = 1607;

  noteToMidi[62] = 293.66;
  noteToPeriod[62] = 1640; //1640
  noteToPeriodDefault[62] = 1703;

  noteToMidi[61] = 277.18;
  noteToPeriod[61] = 1791; // 1791
  noteToPeriodDefault[61] = 1804;

  noteToMidi[60] = 261.63;
  noteToPeriod[60] = 1872; // 1872
  noteToPeriodDefault[60] = 1911;

  noteToMidi[59] = 246.94;
  noteToPeriod[59] = 2000; // 2000
  noteToPeriodDefault[59] = 2025;

  noteToMidi[58] = 233.08;
  noteToPeriod[58] = 2106; // 2106
  noteToPeriodDefault[58] = 2145;

  noteToMidi[57] = 220.00;
  noteToPeriod[57] = 2257; // 2257
  noteToPeriodDefault[57] = 2273;

  noteToMidi[56] = 207.65;
  noteToPeriod[56] = 2408; // 2408
  noteToPeriodDefault[56] = 2408;

  noteToMidi[55] = 196.00;
  noteToPeriod[55] = 2498; // 2498
  noteToPeriodDefault[55] = 2551;

  noteToMidi[54] = 185.00;
  noteToPeriod[54] = 2651; // 2651
  noteToPeriodDefault[54] = 2703;

  noteToMidi[53] = 174.61;
  noteToPeriod[53] = 2808; // 2808
  noteToPeriodDefault[53] = 2864;

  noteToMidi[52] = 164.81;
  noteToPeriod[52] = 2969; // 2969
  noteToPeriodDefault[52] = 3034;

  noteToMidi[51] = 155.56;
  noteToPeriod[51] = 3192; // 3192
  noteToPeriodDefault[51] = 3214;

  noteToMidi[50] = 146.83;
  noteToPeriod[50] = 3355; // 3355
  noteToPeriodDefault[50] = 3405;

  noteToMidi[49] = 138.59;
  noteToPeriod[49] = 3580; // 3580
  noteToPeriodDefault[49] = 3608;

  noteToMidi[48] = 130.81;
  noteToPeriod[48] = 3810; // 3810
  noteToPeriodDefault[48] = 3822;

  noteToMidi[47] = 123.47;
  noteToPeriod[47] = 4000; // 4000
  noteToPeriodDefault[47] = 4050;

  noteToMidi[46] = 116.54;
  noteToPeriod[46] = 4250; // 4250
  noteToPeriodDefault[46] = 4290;

  noteToMidi[45] = 110.00;
  noteToPeriod[45] = 4515; // 4515
  noteToPeriodDefault[45] = 4545;

  noteToMidi[44] = 103.83;
  noteToPeriod[44] = 4756; // 4756
  noteToPeriodDefault[44] = 4816;

  noteToMidi[43] = 98.00;
  noteToPeriod[43] = 5060; // 5060
  noteToPeriodDefault[43] = 5102;

  noteToMidi[42] = 92.50;
  noteToPeriod[42] = 5371; // 5371
  noteToPeriodDefault[42] = 5405;

  noteToMidi[41] = 87.31;
  noteToPeriod[41] = 5684; // 5684
  noteToPeriodDefault[41] = 5727;

  noteToMidi[40] = 82.41;
  noteToPeriod[40] = 6000; // 6000
  noteToPeriodDefault[40] = 6067;

  noteToMidi[39] = 77.78;
  noteToPeriod[39] = 6385; // 6385
  noteToPeriodDefault[39] = 6428;

  noteToMidi[38] = 73.42;
  noteToPeriod[38] = 6775; // 6775
  noteToPeriodDefault[38] = 6810;

  noteToMidi[37] = 69.30;
  noteToPeriod[37] = 7130; // 7130
  noteToPeriodDefault[37] = 7215;

  noteToMidi[36] = 65.41;
  noteToPeriod[36] = 7644; // 7625
  noteToPeriodDefault[36] = 7644;

  noteToMidi[35] = 61.74;
  noteToPeriod[35] = 8098;
  noteToPeriodDefault[35] = 8098;

  noteToMidi[34] = 58.27;
  noteToPeriod[34] = 8581;
  noteToPeriodDefault[34] = 8581;

  noteToMidi[33] = 55.00;
  noteToPeriod[33] = 9091;
  noteToPeriodDefault[33] = 9091;

  noteToMidi[32] = 51.91;
  noteToPeriod[32] = 9632;
  noteToPeriodDefault[32] = 9632;

  noteToMidi[31] = 49.00;
  noteToPeriod[31] = 10204;
  noteToPeriodDefault[31] = 10204;

  noteToMidi[30] = 46.25;
  noteToPeriod[30] = 10811;
  noteToPeriodDefault[30] = 10811;

  noteToMidi[29] = 43.65;
  noteToPeriod[29] = 11455;
  noteToPeriodDefault[29] = 11455;

  noteToMidi[28] = 41.20;
  noteToPeriod[28] = 12136;
  noteToPeriodDefault[28] = 12136;

  noteToMidi[27] = 38.89;
  noteToPeriod[27] = 12857;
  noteToPeriodDefault[27] = 12857;

  noteToMidi[26] = 36.71;
  noteToPeriod[26] = 13620;
  noteToPeriodDefault[26] = 13620;

  noteToMidi[25] = 34.65;
  noteToPeriod[25] = 14430;
  noteToPeriodDefault[25] = 14430;

  noteToMidi[24] = 32.70;
  noteToPeriod[24] = 15291;
  noteToPeriodDefault[24] = 15291;

  noteToMidi[23] = 30.87;
  noteToPeriod[23] = 16197;
  noteToPeriodDefault[23] = 16197;

  noteToMidi[22] = 29.14;
  noteToPeriod[22] = 17159;
  noteToPeriodDefault[22] = 17159;

  noteToMidi[21] = 27.50;
  noteToPeriod[21] = 18182;
  noteToPeriodDefault[21] = 18182;

  noteToMidi[20] = 25.96;
  noteToPeriod[20] = 19260;
  noteToPeriodDefault[20] = 19260;

  noteToMidi[19] = 24.50;
  noteToPeriod[19] = 20408;
  noteToPeriodDefault[19] = 20408;

  noteToMidi[18] = 23.12;
  noteToPeriod[18] = 21626;
  noteToPeriodDefault[18] = 21626;

  noteToMidi[17] = 21.83;
  noteToPeriod[17] = 22904;
  noteToPeriodDefault[17] = 22904;

  noteToMidi[16] = 20.60;
  noteToPeriod[16] = 24272;
  noteToPeriodDefault[16] = 24272;

  noteToMidi[15] = 19.45;
  noteToPeriod[15] = 25707;
  noteToPeriodDefault[15] = 25707;

  noteToMidi[14] = 18.35;
  noteToPeriod[14] = 27248;
  noteToPeriodDefault[14] = 27248;

  noteToMidi[13] = 17.32;
  noteToPeriod[13] = 28868;
  noteToPeriodDefault[13] = 28868;

  noteToMidi[12] = 16.35;
  noteToPeriod[12] = 30581;
  noteToPeriodDefault[12] = 30581;

  noteToMidi[11] = 15.43;
  noteToPeriod[11] = 32404;
  noteToPeriodDefault[11] = 32404;

  noteToMidi[10] = 14.57;
  noteToPeriod[10] = 34317;
  noteToPeriodDefault[10] = 34317;

  noteToMidi[9] = 13.75;
  noteToPeriod[9] = 36364;
  noteToPeriodDefault[9] = 36364;

  noteToMidi[8] = 12.98;
  noteToPeriod[8] = 38521;
  noteToPeriodDefault[8] = 38521;

  noteToMidi[7] = 12.25;
  noteToPeriod[7] = 40816;
  noteToPeriodDefault[7] = 40816;

  noteToMidi[6] = 11.56;
  noteToPeriod[6] = 43253;
  noteToPeriodDefault[6] = 43253;

  noteToMidi[5] = 10.91;
  noteToPeriod[5] = 45830;
  noteToPeriodDefault[5] = 45830;

  noteToMidi[4] = 10.30;
  noteToPeriod[4] = 48544;
  noteToPeriodDefault[4] = 48544;

  noteToMidi[3] = 9.72;
  noteToPeriod[3] = 51440;
  noteToPeriodDefault[3] = 51440;

  noteToMidi[2] = 9.18;
  noteToPeriod[2] = 54466;
  noteToPeriodDefault[2] = 54466;

  noteToMidi[1] = 8.66;
  noteToPeriod[1] = 57737;
  noteToPeriodDefault[1] = 57737;

  noteToMidi[0] = 8.18;
  noteToPeriod[0] = 61125;
  noteToPeriodDefault[0] = 61125;

}

// calculates the frequency according to the midi note number
uint16_t midi2frq(uint8_t number) {
  #ifdef DEBUG
    Serial.print(number);
    Serial.print(" | ");
  #endif

  //double _n = (double)((double)number - (double)69);

 // #ifdef DEBUG
 //   Serial.print(_n);
 //   Serial.print(" | ");
 // #endif

  double d = 0;

  /*if (number < 48) { // c3
    d = pow((double)2, _n / (double)12) * (double)440 * (double) 1.015;
  } else if (number < 57) { // c4
    d = pow((double)2, _n / (double)12) * (double)440 * (double) 1.02;
  } else if (number < 60) { // c5
    d = pow((double)2, _n / (double)12) * (double)440 * (double) 1.025;
  } else {
    d = pow((double)2, _n / (double)12) * (double)440 * (double) 1.04;
  }*/

  //d = pow((double)2, _n / (double)12) * (double)440;
  d = noteToMidi[number];

  #ifdef DEBUG
    Serial.println(d);
  #endif

  return (uint16_t)d;
}

// puts the note into the notes array or removes it if already present
int8_t putNote(int8_t note, uint8_t state) {
  // set note
  for (uint8_t i = 0; i < NUM_VALVES; i++) {
    if (notes[i] == -1 && state == 1) { // note on
      notes[i] = note;
      return i;
    } else if (notes[i] == note && state == 0) { // note off
      //notes[i] = -1;
      for (uint8_t j = i; j < NUM_VALVES - 1; j++) {
        notes[j] = notes[j + 1];
        periods[j] = periods[j + 1];
        states[j] = states[j + 1];
        lastNoteMicros[j] = lastNoteMicros[j + 1];
      }
      notes[NUM_VALVES - 1] = -1;
      periods[NUM_VALVES - 1] = 0;
      states[NUM_VALVES - 1] = 0;
      lastNoteMicros[NUM_VALVES - 1] = 0;
      return i;
    }
  }

  if (state == 0) {
    return -1;
  }

  // remove oldest note and shift other notes
  for (int8_t i = 0; i < NUM_VALVES - 1; i++) {
    notes[i] = notes[i + 1];
  }

  notes[NUM_VALVES - 1] = note;
  return NUM_VALVES - 1;
}

int counter = 0;

// 0 = undefined
// 1 = read status
// 2 = read data 1
// 3 = read data 2
// 4 = handle note on/off
int parseState = 1;

uint8_t statusByte;
uint8_t dataByte1;
uint8_t dataByte2;

void loop() {
  // handle Hardware MIDI IN
  if (Serial1.available() > 0 && parseState != 4) {
    uint8_t inp = Serial1.read();

    if (inp == 0b11111110) { // ignore heartbeats
      #ifdef DEBUG
        Serial.println("ignoring active sensing");
      #endif
    } else {
      switch (parseState) {
        case 0:
          break;
        case 1:
          #ifdef DEBUG
            Serial.print("parseState 1 inp: ");
            Serial.println(inp, BIN);
          #endif
          if ((inp & 0b10000000) == 0b10000000) { // first bit 1 => status byte         
            statusByte = inp;
            parseState = 2;
            break;
          } else {
            #ifdef DEBUG
              Serial.print("Unexpected data byte (instead of status byte), assuming data byte 1: ");
              Serial.println(inp, BIN);
            #endif
          }
        case 2:
          #ifdef DEBUG
            Serial.print("parseState 2 inp: ");
            Serial.println(inp, BIN);
          #endif
          if ((inp & 0b10000000) != 0b10000000) { // first bit 0 => data byte
            parseState = 3;
            dataByte1 = inp;
          } else {
            #ifdef DEBUG
              Serial.print("Unexpected status byte (instead of data byte 1): ");
              Serial.println(inp, BIN);
            #endif
            parseState = 1;
          }
          break;
        case 3:
          #ifdef DEBUG
            Serial.print("parseState 3 inp: ");
            Serial.println(inp, BIN);
          #endif
          if ((inp & 0b10000000) != 0b10000000) { // first bit 0 => data byte
            parseState = 4;
            dataByte2 = inp;
          } else {
            #ifdef DEBUG
              Serial.print("Unexpected status byte (instead of data byte 2): ");
              Serial.println(inp, BIN);
            #endif
            parseState = 1;
          }
          break;
        default: 
          Serial.print("parseState ");
          Serial.print(parseState);
          Serial.print(". Unexpected byte in status X inp: ");
          Serial.println(inp, BIN);
          break;
      }
    }
  }

  if (counter == 100) { 
    counter = 0;
    // handle USB MIDI IN
    midiEventPacket_t rx;
    rx = MidiUSB.read();
    if (rx.byte1 != 0) {
      #ifdef DEBUG
        Serial.print("Reading from USB MIDI: statusByte ");
        Serial.print(rx.byte1, BIN);
        Serial.print(" dataByte1 ");
        Serial.print(rx.byte2, BIN);
        Serial.print(" dataByte2 ");
        Serial.println(rx.byte3, BIN);
        
        char buffer[256];
        sprintf(buffer, "header: 0x%X  b1: %d  b2: %d  b3: %d", rx.header, rx.byte1, rx.byte2, rx.byte3);
        Serial.println(buffer);
      #endif
  
      if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 > 0 && rx.byte2 < 84) { // handle note on // 84 = C6 = 1046.5Hz
        int8_t noteIdx = putNote(rx.byte2, 1);
        //uint16_t frq = midi2frq(rx.byte2);
  
        //periods[noteIdx] = 1000000 / frq / 2;

        if (rx.byte2 == 72) { // todo: made this a CC event
          enableTuning = !enableTuning;
        }
        
        if (enableTuning) {
          periods[noteIdx] = noteToPeriod[rx.byte2];
        } else {
          periods[noteIdx] = noteToPeriodDefault[rx.byte2];
        }


        #ifdef DEBUG
          Serial.println("ONNNNNNN " + String(rx.byte2) + " @ " + String(noteIdx));
        #endif
      } else if (((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) && rx.byte2 < 84) { // handle note off
        #ifdef DEBUG
          int8_t idx = 0;
        #endif
  
        for (int8_t i = 0; i < NUM_VALVES; i++) {
          if (notes[i] != -1 && notes[i] == rx.byte2) {
            putNote(rx.byte2, 0);
            //periods[i] = 0;
  
            #ifdef DEBUG
              idx = i;
            #endif

            #ifdef TUNE
              lastNote = rx.byte2;
              Serial.println("Tune note " + String(rx.byte2) + " current value: " + String(noteToPeriod[rx.byte2]) + " (" + String(noteToMidi[rx.byte2]) + "Hz)" + " (Default: " + noteToPeriodDefault[rx.byte2] + ")");
            #endif
  
            break;
          }
        }
  
        #ifdef DEBUG
          Serial.println("OFFFFFFFF " + String(rx.byte2) + " @ " + String(idx));
        #endif
      } else if ((rx.byte1 & 0xF0) == 0xB0) { // handle CC
        if (rx.byte2 == 7) { // CC 7 = Volume
          analogWrite(OVERALL_VELOCITY_PIN, 255 - (rx.byte3 * 2));
        }
      }
      #ifdef DEBUG
        else {
          char buffer[256];
          sprintf(buffer, "header: 0x%X  b1: %d  b2: %d  b3: %d", rx.byte1, rx.byte2, rx.byte3); 
          Serial.println(buffer);
        }
      #endif 
    }

    // handle hardware MIDI IN
    // handle relevant data
    if (parseState == 4) {
      #ifdef DEBUG
        Serial.print("Reading from Serial1: statusByte ");
        Serial.print(statusByte, BIN);
        Serial.print(" dataByte1 ");
        Serial.print(dataByte1, BIN);
        Serial.print(" dataByte2 ");
        Serial.println(dataByte2, BIN);
      #endif

      if ((statusByte & 0xF0) == 0x90 && dataByte2 > 0 && dataByte1 < 84) { // handle note on
        int8_t noteIdx = putNote(dataByte1, 1);

        if (dataByte1 == 72) { // todo make this a CC event
          enableTuning = !enableTuning;
        }
        
        if (enableTuning) {
          periods[noteIdx] = noteToPeriod[dataByte1];
        } else {
          periods[noteIdx] = noteToPeriodDefault[dataByte1];
        }


        #ifdef DEBUG
          Serial.println("ONNNNNNN " + String(dataByte1) + " @ " + String(noteIdx));
        #endif
      } else if ((statusByte & 0xF0) == 0x80 || ((statusByte & 0xF0) == 0x90 && dataByte2 == 0) && dataByte1 < 84) { // handle note off
          #ifdef DEBUG
            int8_t idx = 0;
          #endif
    
          for (int8_t i = 0; i < NUM_VALVES; i++) {
            if (notes[i] != -1 && notes[i] == dataByte1) {
              putNote(dataByte1, 0);
    
              #ifdef DEBUG
                idx = i;
              #endif

              #ifdef TUNE
                lastNote = dataByte1;
                Serial.println("Tune note " + String(dataByte1) + " current value: " + String(noteToPeriod[dataByte1]) + " (" + String(noteToMidi[dataByte1]) + "Hz)" + " (Default: " + noteToPeriodDefault[dataByte1] + ")");
              #endif
    
              break;
            }
          }
    
          #ifdef DEBUG
            Serial.println("OFFFFFFFF " + String(dataByte1) + " @ " + String(idx));
          #endif
      }

      parseState = 1;
    }
  
  } else {
    counter++;
  }


  // get current microseconds
  uint64_t nowMicros = micros();

  // check for notes that require attention, either note on or note off
  for (uint8_t i = 0; i < NUM_VALVES; i++) {
    if (lastNoteMicros[i] + periods[i] < nowMicros && notes[i] != -1) {
      lastNoteMicros[i] = nowMicros;
      //states[i] ^= 1;
      //digitalWrite(valves[i], states[i]);

      switch (i) {
        case 0: // PD4 // 6mm
        PORTD ^= B00010000;
        break;
        case 1: // PC6 // 6mm
        PORTC ^= B01000000;
        break;
        case 2: // PD7 // 6mm
        PORTD ^= B10000000;
        break;
        case 3: // PE6 // 4mm
        PORTE ^= B01000000;
        break;
        case 4: // PB4 // 4mm
        PORTB ^= B00010000;
        break;
        case 5: // PB5 // 4mm
        PORTB ^= B00100000;
        break;
        case 6: // PD0 // 4mm
        PORTD ^= B00000001;
        break;
        case 7: // PD1 // 4mm
        PORTD ^= B00000010;
        break;
      }
    } else if (notes[i] == -1) { // be sure to end notes that should be ended
      digitalWrite(valves[i], LOW);
    }
  }

  #ifdef TUNE
    if (Serial.available()) {
      String input = Serial.readString();
      if (input.startsWith("+")) {
        Serial.println("inc");
        noteToPeriod[lastNote] = noteToPeriod[lastNote] + 1;
      } else if (input.startsWith("-")) {
        Serial.println("dec");
        noteToPeriod[lastNote] = noteToPeriod[lastNote] - 1;
      } else {
        noteToPeriod[lastNote] = (uint16_t) input.toInt();
      }
      

      Serial.println("Set note " + String(lastNote) + " to " + String(noteToPeriod[lastNote]) + " (Default: " + noteToPeriodDefault[lastNote] + ")");
    }
  #endif
}