Skip to content
Snippets Groups Projects
noteblock.ino 5.7 KiB
Newer Older
#include "MIDIUSB.h" // see https://github.com/arduino-libraries/MIDIUSB
fxk8y's avatar
fxk8y committed
#include <math.h>

#define DEBUG
#undef DEBUG

#define MIDI_IN_PIN 0
#define OVERALL_VELOCITY_PIN 10

#define VALVE_1 7     // 6mm
#define VALVE_2 6     // 6mm
#define VALVE_3 9     // 6mm
#define VALVE_4 8     // 4mm
#define VALVE_5 5     // 4mm

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

// array of the length of the tone period required to create the tone
uint16_t periods[NUM_VALVES] = {0, 0, 0, 0, 0};
fxk8y's avatar
fxk8y committed

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

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

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

// MIDI parameter overview
fxk8y's avatar
fxk8y committed
// 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);
  #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);
fxk8y's avatar
fxk8y committed
}

// calculates the frequency according to the midi note number
fxk8y's avatar
fxk8y committed
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

  /*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;

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

fxk8y's avatar
fxk8y committed
  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) {
  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;
  // 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;
}
fxk8y's avatar
fxk8y committed

int counter = 0;
void loop() {
  if (counter == 100) { 
    counter = 0;
    // handle USB MIDI IN
    midiEventPacket_t rx;
    rx = MidiUSB.read();
    if (rx.byte1 != 0) {
        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);
  
      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;
  
        #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
  
            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));
        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 
  } else {
    counter++;
fxk8y's avatar
fxk8y committed
  }

/*  // handle Hardware MIDI IN
  if (Serial1.available() >= 3) {
    uint8_t statusByte = Serial1.read();
    uint8_t dataByte1 = Serial1.read();
    uint8_t dataByte2 = Serial1.read();

    if ((statusByte & 0x09) == 0x09) { // handle note on

    } else if ((statusByte & 0x08) == 0x08) { // handle note off

    }
  }
  // get current microseconds
  uint64_t nowMicros = micros();
fxk8y's avatar
fxk8y committed

  // check for notes that require attention
  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]);
    } else if (notes[i] == -1) { // be sure to end notes that should be ended
      digitalWrite(valves[i], LOW);
    }
  }