MIDI Library Question: <MIDI.h> versus <usbmidi.h>

Hi @all,

I apologize in advance if I violate any forum conventions here. If that happens I'll be sure to take note moving forward. Seeking to hopefully clear up some of my confusion:

--QUESTION #1--
Does anyone know whether the order of listed parameters for these two libraries (<MIDI.h> and <usbmidi.h>) is arbitrary? Or do I need to pay close attention to the order in which "Channel", "Note/Pitch", and "Velocity" bytes are listed? It seems to me that the <midi.h> library sometimes lists parameters “…(byte channel, byte pitch, byte velocity) respectively, whereas the <usbmidi.h> library lists these as “…(note, velocity, channel)”, respectively:

Example(s): <midi.h>
Code:
void setup() {
MIDI.setHandleNoteOn(handleNoteOn);
...}

void HandleNoteOn(byte channel, byte pitch, byte velocity) {
…}

Example: <usbmidi.h>
Code:
void NoteOnSend (int j) {
  int FSRRead = analogRead(FSRpin [j]);                       
  int velocity = map (FSRRead, 0, 800, MIDIMIN, 127);          
  usbMIDI.sendNoteOn (Note[j], velocity, 10);
}

Sketch(A)Breadboard-min.jpgSketch(B)Breadboard3-min.jpgSketch(C)BreadboardNotWorking2-min.jpg

--QUESTION #2--
Using a Teensy 4.1 w/ Rev D2 Audio Shield attached I've got Sketch(A) successfully saving ".mid" files to a microSD card when supplying MIDI keyboard input via 5-pin DIN (pinRX1<<MIDI-IN). BUT!!!--when substituting 5-pin input with four FSR sensors (Analog pins A17, A16, A15, A14), the resulting microSD ".mid" files no longer contain data.

I probably just need to try a few more example sketches until the key difference(s) sink in. Though as of now I’m trying to append an existing Sketch(A) using <MIDI.h> library functions, with code from an older Sketch(B) calling verbs like “usbMIDI.sendNoteOn”, which I understand is part of the <usbmidi.h> library, graciously explained here:

https://www.pjrc.com/teensy/td_midi.html

Sorry for rambling. Basically, I need (B) to fit inside of (A), in order to write incoming FSR sensor data to .mid files on my Rev D2 Audio Shield's microSD card. Here are the aforementioned sketches, and my failed attempt to combine the two in Sketch(C):

Sketch(A): --working (5-pin DIN MIDI keyboard input >> pinRX1 >> .mid file @ microSD card output)
Code:
// File and MIDI handling
#include <SD.h>
#include <MIDI.h>

// Our Real Time Clock
#include <RTClib.h>
RTC_DS3231 RTC;
bool HAS_RTC = false;

// Audio pins and values
#define AUDIO 8
#define AUDIO_DEBUG_PIN 3
int lastPlayState = 0;
bool play = false;

// Marker pins and values
#define PLACE_MARKER_PIN 5
int lastMarkState = 0;
int nextMarker = 1;

const int chipSelect = 10;
#define CHIP_SELECT 10
#define HAS_MORE_BYTES 0x80

#define NOTE_OFF_EVENT 0x80
#define NOTE_ON_EVENT 0x90
#define CONTROL_CHANGE_EVENT 0xB0
#define PITCH_BEND_EVENT 0xE0

// we use a 2 minute idling timeout (in millis)
#define RECORDING_TIMEOUT 120000
unsigned long lastLoopCounter = 0;
unsigned long loopCounter = 0;

unsigned long startTime = 0;
unsigned long lastTime = 0;

#define FILE_FLUSH_INTERVAL 400
String filename;
File file;

MIDI_CREATE_DEFAULT_INSTANCE();

/**
   Set up our inline MIDI recorder
*/
void setup() {
  // set up MIDI handling
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandlePitchBend(handlePitchBend);
  MIDI.setHandleControlChange(handleControlChange);

  // set up the tone playing button
  pinMode(AUDIO_DEBUG_PIN, INPUT);
  pinMode(AUDIO, OUTPUT);
  // tone(AUDIO, 440, 200);

  // set up the MIDI marker button
  pinMode(PLACE_MARKER_PIN, INPUT);

  // set up RTC interfacing
  if (RTC.begin()) {
    // uncomment this line to set the current date/time on the RTC
    // RTC.adjust(DateTime(F(__DATE__), F(__TIME__)));

    // if the RTC works, we can tell the SD library
    // how it can check for the current time when it
    // needs timestamping for file creation/writing.
    SdFile::dateTimeCallback(dateTime);
    HAS_RTC = true;
    // tone(AUDIO, 880, 100);
  }

  // set up SD card functionality and allocate a file
  pinMode(CHIP_SELECT, OUTPUT);
  if (SD.begin(CHIP_SELECT)) {
    creatNextFile();
    if (file) {
      writeMidiPreamble();
      // tone(AUDIO, 1760, 100);
    }
  }
}

void dateTime(uint16_t* date, uint16_t* time) {
  DateTime d = RTC.now();
  *date = FAT_DATE(d.year(), d.month(), d.day());
  *time = FAT_TIME(d.hour(), d.minute(), d.second());
}


/**
    We could use the EEPROM to store this number,
    but since we're not going to get timestamped
    files anyway, just looping is also fine.
*/
void creatNextFile() {
  for (int i = 1; i < 1000; i++) {
    filename = "file-";
    if (i < 10) filename += "0";
    if (i < 100) filename += "0";
    filename += String(i);
    filename += String(".mid");

    if (!SD.exists(filename.c_str())) {
      file = SD.open(filename.c_str(), FILE_WRITE);
      return;
    }
  }
}

/**
   Set up a new MIDI file with some boiler plate byte code
*/
void writeMidiPreamble() {
  byte header[] = {
    0x4D, 0x54, 0x68, 0x64,   // "MThd" chunk
    0x00, 0x00, 0x00, 0x06,   // chunk length (from this point on)
    0x00, 0x00,               // format 0
    0x00, 0x01,               // one track
    0x01, 0xD4                // data rate = 458 ticks per quarter note
  };
  file.write(header, 14);

  byte track[] = {
    0x4D, 0x54, 0x72, 0x6B,   // "MTrk" chunk
    0x00, 0x00, 0x00, 0x00    // chunk length placeholder (MSB)
  };
  file.write(track, 8);

  byte tempo[] = {
    0x00,                     // time delta (of zero)
    0xFF, 0x51, 0x03,         // tempo op code
    0x06, 0xFD, 0x1F          // real rate = 458,015μs per quarter note (= 134.681 BPM)
  };
  file.write(tempo, 7);
}

/**
   The program loop consists of flushing our file to disk,
   checking our buttons to see if they just got pressed,
   and then handling MIDI input, if there is any.
*/
void loop() {
  checkForMarker();
  setPlayState();
  updateFile();
  MIDI.read();
}


// ======================================================================================


/**
   We flush the file's in-memory content to disk
   every 400ms, allowing. That way if we take the
   SD card out, it's basically impossible for any
   data to have been lost.
*/
void updateFile() {
  loopCounter = millis();
  if (loopCounter - lastLoopCounter > FILE_FLUSH_INTERVAL) {
    checkReset();
    lastLoopCounter = loopCounter;
    file.flush();
  }
}

/**
   This "function" would normally crash any kernel that tries
   to run it by violating memory access. Instead, the Arduino's
   watchdog will auto-reboot, giving us a software "reset".
*/
void(* resetArduino) (void) = 0;

/**
  if we've not received any data for 2 minutes, and we were
  previously recording, we reset the arduino so that when
  we start playing again, we'll be doing so in a new file,
  rather than having multiple sessions with huge silence
  between them in the same file.
*/
void checkReset() {
  if (startTime == 0) return;
  if (!file) return;
  if (millis() - lastTime > RECORDING_TIMEOUT) {
    file.close();
    resetArduino();
  }
}

/**
   A little audio-debugging: pressing the button tied to the
   audio debug pin will cause the program to play notes for
   every MIDI note-on event that comes flying by.
*/
void setPlayState() {
  int playState = digitalRead(AUDIO_DEBUG_PIN);
  if (playState != lastPlayState) {
    lastPlayState = playState;
    if (playState == 1) {
      play = !play;
    }
  }
}

/**
   This checks whether the MIDI marker button got pressed,
   and if so, writes a MIDI marker message into the track.
*/
void checkForMarker() {
  int markState = digitalRead(PLACE_MARKER_PIN);
  if (markState  != lastMarkState) {
    lastMarkState = markState;
    if (markState == 1) {
      writeMidiMarker();
    }
  }
}

/**
  Write a MIDI marker to file, by writing a delta, then
  the op code for "midi marker", the number of letters
  the marker label has, and then the label (using ASCII).

  For simplicity, the marker labels will just be a
  sequence number starting at "1".
*/
void writeMidiMarker() {
  if (!file) return;

  // delta + event code
  writeVarLen(file, getDelta());
  file.write(0xFF);
  file.write(0x06);

  // If we have an RTC available, we can write the clock time
  // Otherwise,  write a sequence number.

  if (HAS_RTC) {
    DateTime d = RTC.now();
    byte len = 20;
    writeVarLen(file, len);

    char marker[len]; // will hold strings like "2021/01/23, 10:53:31"
    sprintf(marker, "%04d/%02d/%02d, %02d:%02d:%02d", d.year(), d.month(), d.day(), d.hour(), d.minute(), d.second());
    file.write(marker, len);
  }

  else {
    // how many letters are we writing?
    byte len = 1;
    if (nextMarker > 9) len++;
    if (nextMarker > 99) len++;
    if (nextMarker > 999) len++;
    writeVarLen(file, len);

    // our label:
    byte marker[len];
    String(nextMarker++).getBytes(marker, len);
    file.write(marker, len);
  }
}


// ======================================================================================


void handleNoteOff(byte channel, byte pitch, byte velocity) {
  writeToFile(NOTE_OFF_EVENT, pitch, velocity, getDelta());
}

void handleNoteOn(byte channel, byte pitch, byte velocity) {
  writeToFile(NOTE_ON_EVENT, pitch, velocity, getDelta());
  if (play) tone(AUDIO, 440 * pow(2, (pitch - 69.0) / 12.0), 100);
}

void handleControlChange(byte channel, byte cc, byte value) {
  writeToFile(CONTROL_CHANGE_EVENT, cc, value, getDelta());
}

void handlePitchBend(byte channel, int bend) {
  bend += 0x2000; // MIDI bend uses the range 0x0000-0x3FFF, with 0x2000 as center.
  byte lsb = bend & 0x7F;
  byte msb = bend >> 7;
  writeToFile(PITCH_BEND_EVENT, lsb, msb, getDelta());
}

/**
   This calculates the number of ticks since the last MIDI event
*/
int getDelta() {
  if (startTime == 0) {
    // if this is the first event, even if the Arduino's been
    // powered on for hours, this should be delta zero.
    startTime = millis();
    lastTime = startTime;
    return 0;
  }
  unsigned long now = millis();
  unsigned int delta = (now - lastTime);
  lastTime = now;
  return delta;
}

/**
   Write "common" MIDI events to file, where common MIDI events
   all use the following data format:

     <delta> <event code> <byte> <byte>

   See the "Standard MIDI-File Format" for more information.
*/
void writeToFile(byte eventType, byte b1, byte b2, int delta) {
  if (!file) return;
  writeVarLen(file, delta);
  file.write(eventType);
  file.write(b1);
  file.write(b2);
}

/**
   Encode a unsigned 32 bit integer as variable-length byte sequence
   of, at most, 4 7-bit-with-has-more bytes. This function is supplied
   as part of the MIDI file format specification.
*/
void writeVarLen(File file, unsigned long value) {
  // capture the first 7 bit block
  unsigned long buffer = value & 0x7f;

  // shift in 7 bit blocks with "has-more" bit from the
  // right for as long as `value` has more bits to encode.
  while ((value >>= 7) > 0) {
    buffer <<= 8;
    buffer |= HAS_MORE_BYTES;
    buffer |= value & 0x7f;
  }

  // Then unshift bytes one at a time for as long as the has-more bit is high.
  while (true) {
    file.write((byte)(buffer & 0xff));
    if (buffer & HAS_MORE_BYTES) {
      buffer >>= 8;
    } else {
      break;
    }
  }
}

Sketch(B): --working (FSR sensor input >> analog pins >> MIDI+audio output)
Code:
// Libraries
#include "AudioSampleKickkhronos.h"
#include "AudioSampleHh1khronos.h"
#include "AudioSampleSnarekhronos.h"
#include "AudioSampleHh2khronos.h"
#include <Bounce.h> // Don't think I still need this. TBD.
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlayMemory          playMem3;       //xy=248.75,260.0000057220459
AudioPlayMemory          playMem4;       //xy=248.75,325.0000057220459
AudioPlayMemory          playMem2;       //xy=251.25,193.7500057220459
AudioPlayMemory          playMem1;       //xy=256.25,135.0000057220459
AudioMixer4              mixer1;         //xy=498.75000762939453,230.00000381469727
AudioOutputI2S           i2s1;           //xy=735.0000114440918,230.00000190734863
AudioConnection          patchCord1(playMem3, 0, mixer1, 2);
AudioConnection          patchCord2(playMem4, 0, mixer1, 3);
AudioConnection          patchCord3(playMem2, 0, mixer1, 1);
AudioConnection          patchCord4(playMem1, 0, mixer1, 0);
AudioConnection          patchCord5(mixer1, 0, i2s1, 0);
AudioConnection          patchCord6(mixer1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=433.75000762939453,493.7500057220459
// GUItool: end automatically generated code

//// GLOBAL VARIABLES ////
// INDEX # -->  0     1     2    3
int FSRpin[] = {A17, A16, A15, A14};  /*"Relevant analog pins connected to FSRs"
                                      --"FSRpin[]" later gets read by "analogRead(..." function 
                                      --in void loop(){} section! */
const int FSRs = 4;                   /*number of FSRs // 
                                      // --"FSRs" later called in Condition 
                                      // --part of For-Loop in void loop(){} section!*/
int Note [] = {60, 61, 62, 63};       /*MIDI note numbers ... listed in order of FSRpin*/
int counter [FSRs];                   /*"counter[4]", in other words.*/
int VELMASK = 0;
int ATMASK = 0;
int AFTERTHRESH = 50;         /*"Analog sensor value above which aftertouch messages are sent"
                              50 in decimal = 0110010 in binary*/

int THRESH = 45;              /*"Analog sensor value above which note / velocity is recognised"
                              --"THRESH", an integer-type variable, is later 
                              --called in void loop() {...}
                              45 in decimal = 0101101 in binary)*/

int VELTIME = 500;            /*"Counter value at which point velocity is sampled ...
                              counter is zero when first touched, velocity is sampled X ticks later
                              500 ticks sounds like a lot, but the teensy LC is clocked at 48Mhz"       
                              500 in decimal (0111110100 binary)*/

int AFTERTIME = 2500;         /*"Counter value at which point aftertouch is sampled ...
                              every X ticks of a touch, until released ...
                              you don't want too many aftertouch messages per touch, 
                              and 2500 gives a surprising number"
                              2500 in decimal (0100111000100 in binary)*/
int MIDIMIN = 20;             /*bottom MIDI value for the MIDI velocity AND aftertouch messages*/
void NoteOnSend (int);        /*One of 3 main functions in addition to void setup(); and void loop();*/
void PolyTouchSend (int);     /*One of 3 main functions in addition to void setup(); and void loop();*/

////following global variables relating to Wav2Sketch/output audio:
int channel [] = {0, 1, 2, 3};                /*Mixer channels for wav2sketch audio samples, listed 
                                              in order of their respective .wav samples & analog pins*/
const int SAMPLEs = 4;
int counter2 [SAMPLEs];  
void SampleOnSend (int);                      /*One of 3 main functions in addition to void setup(); and void loop(); */
void gain (unsigned int channel, float gain); /*Function that goes within void SampleOnSend(int); function. 
                                              Confusing to set up because the names for second parameter 
                                              "gain" varied as "level" in the ArduinoIDE>TeensyExample sketches*/

void setup() {
  Serial.begin (32500);

  AudioMemory(10);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  mixer1.gain(0, 0.4);
  mixer1.gain(1, 0.4);
  mixer1.gain(2, 0.4);
  mixer1.gain(3, 0.4);
}

void loop () {
  for (int i = 0; i < FSRs; i++) {
    int FSRRead = analogRead(FSRpin[i]);
    if (FSRRead > THRESH) {
      counter[i] ++;
      if (!(VELMASK & (1 << i)) && (counter[i] == VELTIME)) {   /*"(1 << i)" means "(1x2)^i", and is: 1 if 
                                                                i=0, 2 if i=1, 4 if i=2, or 8 if i=3*/
        VELMASK |= (1 << i);                   
        counter [i] = 0;
        NoteOnSend (i);
        SampleOnSend (i);                                       /*Key to getting variable-velocity wav2sketch 
                                                                samples playing on Adrian's FSR sketch*/
        }
      if (counter [i] == AFTERTIME) {
        counter [i] = 0;
        PolyTouchSend(i);
      }
    }
    else {                                                      /*When FSRRead(0-1023) is NOT greater than 45, 
                                                                do this: {...}*/
      if (VELMASK & (1 << i)) {          
        usbMIDI.sendNoteOff (Note[i], 0, 10); 
        VELMASK &= ~ (1 << i);                 
        counter [i] = 0;
      }
    }
  }
}

void NoteOnSend (int j) {
  int FSRRead = analogRead(FSRpin [j]);                         /*"int FSRRead": range of 0 to 1023*/
  int velocity = map (FSRRead, 0, 800, MIDIMIN, 127);           /*For input range why not 0 to 1023?*/
  usbMIDI.sendNoteOn (Note[j], velocity, 10);
}


void SampleOnSend (int k) {                                     /*Function added to Adrian's sketch for getting 
                                                                audio samples to play simulataneously with existing 
                                                                MIDI output signal coded in void 
                                                                NoteOnSend(int j) function*/
  analogReadResolution(7);
  int FSRRead2 = analogRead(FSRpin [k]);
  float gain = map (FSRRead2, 0, 127, .2, 1.0);
  mixer1.gain(channel[k], gain);
  if (analogRead(A17) >= THRESH) {
    playMem1.play(AudioSampleHh2khronos); }
  if (analogRead(A16) >= THRESH) {
    playMem2.play(AudioSampleSnarekhronos); }
  if (analogRead(A15) >= THRESH) {
    playMem3.play(AudioSampleHh1khronos); }
  if (analogRead(A14) >= THRESH) {
    playMem4.play(AudioSampleKickkhronos); }
}

void PolyTouchSend (int j) {                                     /*Don't think this is needed, but why mess with 
                                                                  what's working right?*/
  int FSRRead = analogRead(FSRpin [j]);
  if (FSRRead > AFTERTHRESH) {
    int aftertouch = map (FSRRead, 0, 800, MIDIMIN, 127);        /*Again, why input range of 0-800?*/
    usbMIDI.sendPolyPressure (Note[j], aftertouch, 10);
  }
}

Sketch(C): -- not working (FSR sensor input >> analog pins >> MIDI+audio ANNND .mid file @ microSD card output)

(Pardon my ridiculous use of "//" and "/*...*/")

Code:
////LIBRARIES

// File and MIDI handling
#include <SD.h>
#include <usb_midi.h>
#include <MIDI.h>

// from Sketch(B)
#include "AudioSampleKickkhronos.h"
#include "AudioSampleHh1khronos.h"
#include "AudioSampleSnarekhronos.h"
#include "AudioSampleHh2khronos.h"
#include <Bounce.h> // Don't think I still need this. TBD.
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// Our Real Time Clock
#include <RTClib.h>
RTC_DS3231 RTC;
bool HAS_RTC = false;

// GUItool: begin automatically generated code
AudioPlayMemory          playMem3;       //xy=248.75,260.0000057220459
AudioPlayMemory          playMem4;       //xy=248.75,325.0000057220459
AudioPlayMemory          playMem2;       //xy=251.25,193.7500057220459
AudioPlayMemory          playMem1;       //xy=256.25,135.0000057220459
AudioMixer4              mixer1;         //xy=498.75000762939453,230.00000381469727
AudioOutputI2S           i2s1;           //xy=735.0000114440918,230.00000190734863
AudioConnection          patchCord1(playMem3, 0, mixer1, 2);
AudioConnection          patchCord2(playMem4, 0, mixer1, 3);
AudioConnection          patchCord3(playMem2, 0, mixer1, 1);
AudioConnection          patchCord4(playMem1, 0, mixer1, 0);
AudioConnection          patchCord5(mixer1, 0, i2s1, 0);
AudioConnection          patchCord6(mixer1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=433.75000762939453,493.7500057220459
// GUItool: end automatically generated code

//// GLOBAL VARIABLES ////
// INDEX # -->  0     1     2    3
int FSRpin[] = {A17, A16, A15, A14};  /*"Relevant analog pins connected to FSRs"
                                      --"FSRpin[]" later gets read by "analogRead(..." function 
                                      --in void loop(){} section! */
const int FSRs = 4;  
int Note [] = {60, 61, 62, 63};       /*MIDI note numbers ... listed in order of FSRpin*/
int counter [FSRs];                   /*"counter[4]", in other words.*/
int VELMASK = 0;
int ATMASK = 0;
int AFTERTHRESH = 50;         /*"Analog sensor value above which aftertouch messages are sent"
                              50 in decimal = 0110010 in binary*/
int THRESH = 45;              /*"Analog sensor value above which note / velocity is recognised"
                              --"THRESH", an integer-type variable, is later 
                              --called in void loop() {...}
                              45 in decimal = 0101101 in binary)*/
int VELTIME = 500;            /*"Counter value at which point velocity is sampled ...
                              counter is zero when first touched, velocity is sampled X ticks later
                              500 ticks sounds like a lot, but the teensy LC is clocked at 48Mhz"       
                              500 in decimal (0111110100 binary)*/
int AFTERTIME = 2500;         /*"Counter value at which point aftertouch is sampled ...
                              every X ticks of a touch, until released ...
                              you don't want too many aftertouch messages per touch, 
                              and 2500 gives a surprising number"
                              2500 in decimal (0100111000100 in binary)*/
int MIDIMIN = 20;             /*bottom MIDI value for the MIDI velocity AND aftertouch messages*/

//Functions declared pre-"void setup() {}"
void NoteOnSend (int);        /*One of 3 main functions in addition to void setup(); and void loop();*/
void PolyTouchSend (int);     /*One of 3 main functions in addition to void setup(); and void loop();*/
//void NoteOffSend (int);

//void handleNoteOff (int);
//void handleNoteOn (int);
//void handlePitchBend (int);
//void handleControlChange (int);

////following global variables relating to Wav2Sketch/output audio:
int channel [] = {0, 1, 2, 3};                /*Mixer channels for wav2sketch audio samples, listed 
                                              in order of their respective .wav samples & analog pins*/
const int SAMPLEs = 4;
int counter2 [SAMPLEs];  
void SampleOnSend (int);                      /*One of 3 main functions in addition to void setup(); and void loop(); */
void gain (unsigned int channel, float gain); /*Function that goes within void SampleOnSend(int); function. 
                                              Confusing to set up because the names for second parameter 
                                              "gain" varied as "level" in the ArduinoIDE>TeensyExample sketches*/




// Audio pins and values
#define AUDIO 8                                     // want to change this to wav2Sketch's output sounds, instead of Buzzer
#define AUDIO_DEBUG_PIN 3                           // audio debug Button corresponding to Buzzer, pin3
int lastPlayState = 0;
bool play = false;

// Marker pins and values
#define PLACE_MARKER_PIN 5                          //MIDI Marker Button, pin5
int lastMarkState = 0;
int nextMarker = 1;

const int chipSelect = 10;
#define CHIP_SELECT 10
#define HAS_MORE_BYTES 0x80                          //write varlen function related // "sprintf" macro related?

#define NOTE_OFF_EVENT 0x80
#define NOTE_ON_EVENT 0x90
#define CONTROL_CHANGE_EVENT 0xB0
#define PITCH_BEND_EVENT 0xE0
//#define POLY_TOUCH_EVENT 0xA0

// we use a 2 minute idling timeout (in millis)
#define RECORDING_TIMEOUT 120000
unsigned long lastLoopCounter = 0;
unsigned long loopCounter = 0;

unsigned long startTime = 0;                        //timer related global variables
unsigned long lastTime = 0;                         //timer related global variables

#define FILE_FLUSH_INTERVAL 400                     //millisecond interval in which file(s) are written to microSD card
String filename;
File file;                                          //creates an object called "file"

MIDI_CREATE_DEFAULT_INSTANCE();               // This is required to set up the MIDI.h library.
                                              // The default MIDI setup uses the built-in serial port.

/**
   Set up our inline MIDI recorder
*/
void setup() {

  //Serial.begin (32500);

  AudioMemory(10);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  mixer1.gain(0, 0.4);
  mixer1.gain(1, 0.4);
  mixer1.gain(2, 0.4);
  mixer1.gain(3, 0.4);

  // set up MIDI handling
  //MIDI.begin(MIDI_CHANNEL_OMNI);              //This listens to all MIDI channels. They can be filtered out later...
                                              //MIDI.turnThruOff();  // Disable automatic MIDI Thru function

  /*
  //MIDI.h version of "usbMIDI.setHandle..." functions:
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandlePitchBend(handlePitchBend);
  MIDI.setHandleControlChange(handleControlChange);
  */

 
  /* For each function you create, you must use the corresponding 
  "setHandle" functions to tell usbMIDI to call it when that message 
  type is read. -- https://www.pjrc.com/teensy/td_midi.html -- 
  */
  usbMIDI.begin();
  // set up usbMIDI handling (instead of MIDI handling)
  
  usbMIDI.setHandleNoteOn(handleNoteOn);                // "...(handleNoteOn)" used instead of PJRC suggested "...(myNoteOn)"
  usbMIDI.setHandleNoteOff(handleNoteOff);
  usbMIDI.setHandlePitchChange(handlePitchBend);
  usbMIDI.setHandleControlChange(handleControlChange);
  //usbMIDI.setHandleAfterTouchPoly(handleAfterTouchPoly);    // this is in addition to the four messages MIDIFieldRecorder sketch originally uses

  // set up the tone playing button
  pinMode(AUDIO_DEBUG_PIN, INPUT);            //related to audio debug pin3
  pinMode(AUDIO, OUTPUT);                     //related to audio buzzer output pin8
  tone(AUDIO, 440, 200);

  // set up the MIDI marker button
  pinMode(PLACE_MARKER_PIN, INPUT);           //related to maker button, pin5

  // set up RTC interfacing
  if (RTC.begin()) {
    // uncomment this line to set the current date/time on the RTC
    // RTC.adjust(DateTime(F(__DATE__), F(__TIME__)));

    // if the RTC works, we can tell the SD library
    // how it can check for the current time when it
    // needs timestamping for file creation/writing.
    SdFile::dateTimeCallback(dateTime);
    HAS_RTC = true;
    tone(AUDIO, 880, 100);
  }

  // set up SD card functionality and allocate a file
  pinMode(CHIP_SELECT, OUTPUT);
  if (SD.begin(CHIP_SELECT)) {
    creatNextFile();
    if (file) {
      writeMidiPreamble();
      tone(AUDIO, 1760, 100);
    }
  }
}

void dateTime(uint16_t* date, uint16_t* time) {
  DateTime d = RTC.now();
  *date = FAT_DATE(d.year(), d.month(), d.day());
  *time = FAT_TIME(d.hour(), d.minute(), d.second());
}


/**
    We could use the EEPROM to store this number,
    but since we're not going to get timestamped
    files anyway, just looping is also fine.
*/
void creatNextFile() {
  for (int i = 1; i < 1000; i++) {
    filename = "file-";
    if (i < 10) filename += "0";
    if (i < 100) filename += "0";
    filename += String(i);
    filename += String(".mid");

    if (!SD.exists(filename.c_str())) {
      file = SD.open(filename.c_str(), FILE_WRITE);
      return;
    }
  }
}

/**
   Set up a new MIDI file with some boiler plate byte code
*/
void writeMidiPreamble() {
  byte header[] = {
    0x4D, 0x54, 0x68, 0x64,   // "MThd" chunk
    0x00, 0x00, 0x00, 0x06,   // chunk length (from this point on)
    0x00, 0x00,               // format 0
    0x00, 0x01,               // one track
    0x01, 0xD4                // data rate = 458 ticks per quarter note
  };
  file.write(header, 14);     /*size_t is a data type capable of 
                              representing the size of any object 
                              in bytes. Examples of the use of size_t 
                              are the return type of sizeof() and 
                              Serial.*/

  byte track[] = {
    0x4D, 0x54, 0x72, 0x6B,   // "MTrk" chunk
    0x00, 0x00, 0x00, 0x00    // chunk length placeholder (MSB)
  };
  file.write(track, 8);

  byte tempo[] = {
    0x00,                     // time delta (of zero)
    0xFF, 0x51, 0x03,         // tempo op code
    0x06, 0xFD, 0x1F          // real rate = 458,015μs per quarter note (= 134.681 BPM)
  };
  file.write(tempo, 7);
}

/**
   The program loop consists of flushing our file to disk,
   checking our buttons to see if they just got pressed,
   and then handling MIDI input, if there is any.
*/
void loop() {
  checkForMarker();           //#1 related to marker pin5
  setPlayState();             //#2 related to audio debug pin3_pin8
  updateFile();               //#3 related to flushing files to microSD card
  MIDI.read();             //#4
  
  /*
    Once everything is set up, you then need the "read" function 
    to actually read data. The callback functions are only called 
    when you use read. There are 2 choices for read:
  */
  //usbMIDI.read();         /* (1) All Channels. */
  //usbMIDI.read(0);    /* (2) One Specific Channel (1 to 16). // or " int usb_midi_read(uint32_t channel); "?
                                //Leave blank to listen to all channels) */

  // Velocity Sensing Of Analog Pin Array (4 FSR Sensors)
  for (int i = 0; i < FSRs; i++) {
    int FSRRead = analogRead(FSRpin[i]);
    if (FSRRead > THRESH) {
      counter[i] ++;
      if (!(VELMASK & (1 << i)) && (counter[i] == VELTIME)) {   /*"(1 << i)" means "(1x2)^i", and is: 1 if 
                                                                i=0, 2 if i=1, 4 if i=2, or 8 if i=3*/
        VELMASK |= (1 << i);                   
        counter [i] = 0;
        NoteOnSend (i);
        SampleOnSend (i);                                       /*Key to getting variable-velocity wav2sketch 
                                                                samples playing on Adrian's FSR sketch*/
        }
      if (counter [i] == AFTERTIME) {
        counter [i] = 0;
        PolyTouchSend(i);
      }
    }
    else {                                                      /*When FSRRead(0-1023) is NOT greater than 45, 
                                                                do this: {...}*/
      if (VELMASK & (1 << i)) {     
        //NoteOffSend (i);
        usbMIDI.sendNoteOff (Note[i], 0, 0); 
        VELMASK &= ~ (1 << i);                 
        counter [i] = 0;
      }
    }
  }
}

// ======================================================================================
////needs to combinevvv
void NoteOnSend (int j) {
  int FSRRead = analogRead(FSRpin [j]);                         // "int FSRRead": range of 0 to 1023
  int velocity = map (FSRRead, 0, 800, MIDIMIN, 127);           // For input range why not 0 to 1023?
  //handleNoteOn;
  usbMIDI.sendNoteOn (Note[j], velocity, 0);
}

void SampleOnSend (int k) {                                     /*Debugging, but also User-feedback function appended 
                                                                to Adrian's sketch for getting audio samples 
                                                                to play simulataneously with existing MIDI 
                                                                output signal coded in void NoteOnSend(int j) 
                                                                function*/
  analogReadResolution(7);
  int FSRRead2 = analogRead(FSRpin [k]);
  float gain = map (FSRRead2, 0, 127, .2, 1.0);
  mixer1.gain(channel[k], gain);
  if (analogRead(A17) >= THRESH) {
    playMem1.play(AudioSampleHh2khronos); }
  if (analogRead(A16) >= THRESH) {
    playMem2.play(AudioSampleSnarekhronos); }
  if (analogRead(A15) >= THRESH) {
    playMem3.play(AudioSampleHh1khronos); }
  if (analogRead(A14) >= THRESH) {
    playMem4.play(AudioSampleKickkhronos); }
}

void PolyTouchSend (int j) {                                     //Don't think this is needed, but why mess with 
                                                                  // what's working right?
  int FSRRead = analogRead(FSRpin [j]);
  if (FSRRead > AFTERTHRESH) {
    int aftertouch = map (FSRRead, 0, 800, MIDIMIN, 127);        //Again, why input range of 0-800?
    usbMIDI.sendPolyPressure (Note[j], aftertouch, 1);
  }
}

// ======================================================================================


/**
   We flush the file's in-memory content to disk
   every 400ms, allowing. That way if we take the
   SD card out, it's basically impossible for any
   data to have been lost.
*/
void updateFile() {           //#3 related to flushing files to microSD card
  loopCounter = millis();
  if (loopCounter - lastLoopCounter > FILE_FLUSH_INTERVAL) {
    checkReset();
    lastLoopCounter = loopCounter;
    file.flush();
  }
}

/**
   This "function" would normally crash any kernel that tries
   to run it by violating memory access. Instead, the Arduino's
   watchdog will auto-reboot, giving us a software "reset".
*/
void(* resetArduino) (void) = 0;    //#3 related to flushing files to microSD card

/**
  if we've not received any data for 2 minutes, and we were
  previously recording, we reset the arduino so that when
  we start playing again, we'll be doing so in a new file,
  rather than having multiple sessions with huge silence
  between them in the same file.
*/
void checkReset() {                 //#3 related to flushing files to microSD card
  if (startTime == 0) return;
  if (!file) return;
  if (millis() - lastTime > RECORDING_TIMEOUT) {
    file.close();
    resetArduino();
  }
}

/**
   A little audio-debugging: pressing the button tied to the
   audio debug pin will cause the program to play notes for
   every MIDI note-on event that comes flying by.
*/
// Audio Feedback Debugging. I can use this at some point to toggle the Wav2Sketch audio samples via Rev D2 Audio Shield's Aux port on/off
void setPlayState() {               //#2 related to audio debug pin3_pin8
  int playState = digitalRead(AUDIO_DEBUG_PIN);           
  if (playState != lastPlayState) {
    lastPlayState = playState;
    if (playState == 1) {
      play = !play;
    }
  }
}

/**
   This checks whether the MIDI marker button got pressed,
   and if so, writes a MIDI marker message into the track.
*/
void checkForMarker() {             //#1 related to marker pin5
  int markState = digitalRead(PLACE_MARKER_PIN);
  if (markState  != lastMarkState) {
    lastMarkState = markState;
    if (markState == 1) {
      writeMidiMarker();
    }
  }
}

/**
  Write a MIDI marker to file, by writing a delta, then
  the op code for "midi marker", the number of letters
  the marker label has, and then the label (using ASCII).

  For simplicity, the marker labels will just be a
  sequence number starting at "1".
*/
void writeMidiMarker() {            //#1 related to marker pin5
  if (!file) return;

  // delta + event code
  writeVarLen(file, getDelta());
  file.write(0xFF);
  file.write(0x06);

  // If we have an RTC available, we can write the clock time
  // Otherwise,  write a sequence number.

  if (HAS_RTC) {
    DateTime d = RTC.now();
    byte len = 20;
    writeVarLen(file, len);

    char marker[len]; // will hold strings like "2021/01/23, 10:53:31"
    sprintf(marker, "%04d/%02d/%02d, %02d:%02d:%02d", d.year(), d.month(), d.day(), d.hour(), d.minute(), d.second());
    file.write(marker, len);
  }

  else {
    // how many letters are we writing?
    byte len = 1;
    if (nextMarker > 9) len++;
    if (nextMarker > 99) len++;
    if (nextMarker > 999) len++;
    writeVarLen(file, len);

    // our label:
    byte marker[len];
    String(nextMarker++).getBytes(marker, len);
    file.write(marker, len);
  }
}


// ======================================================================================


/* COMMENT:
  "void handleNoteOn(...)" specified 
  under void setup() within the specific 
  message type, ex) MIDI.setHandleNoteOn(handleNoteOn); 
*/

void handleNoteOff (byte channel, byte pitch, byte velocity) {
  writeToFile(NOTE_OFF_EVENT, pitch, velocity, getDelta());
  }

//MIDIFieldRecorder's MIDI message originally pointed to MIDI.h -- now is usb_MIDI.h
void handleNoteOn(byte channel, byte pitch, byte velocity) {
  writeToFile(NOTE_ON_EVENT, pitch, velocity, getDelta());
  if (play) tone(AUDIO, 440 * pow(2, (pitch - 69.0) / 12.0), 100);
}

void handleControlChange(byte channel, byte cc, byte value) {
  writeToFile(CONTROL_CHANGE_EVENT, cc, value, getDelta());
}

void handlePitchBend(byte channel, int bend) {
  bend += 0x2000; // MIDI bend uses the range 0x0000-0x3FFF, with 0x2000 as center.
  byte lsb = bend & 0x7F;
  byte msb = bend >> 7;
  writeToFile(PITCH_BEND_EVENT, lsb, msb, getDelta());
}

/*
//drafted based on MIDIFieldRecorder's MIDI message format...get rid of this if troubleshooting
void handleAfterTouchPoly(byte channel, byte pitch, byte pressure) {
  writeToFile(POLY_TOUCH_EVENT, pitch, pressure, getDelta());
}
*/

//===================================================================

/**
   This calculates the number of ticks since the last MIDI event
*/
int getDelta() {
  if (startTime == 0) {
    // if this is the first event, even if the Arduino's been
    // powered on for hours, this should be delta zero.
    startTime = millis();
    lastTime = startTime;
    return 0;
  }
  unsigned long now = millis();
  unsigned int delta = (now - lastTime);
  lastTime = now;
  return delta;
}

/**
   Write "common" MIDI events to file, where common MIDI events
   all use the following data format:

     <delta> <event code> <byte> <byte>

   See the "Standard MIDI-File Format" for more information.

    <MTrk event> = <delta-time><event>

      <delta-time> is stored as a variable-length quantity. 
      It represents the amount of time before the following event. 
      If the first event in a track occurs at the very beginning 
      of a track, or if two events occur simultaneously, a delta-time 
      of zero is used. Delta-times are always present. (Not storing 
      delta-times of 0 requires at least two bytes for any other 
      value, and most delta-times aren't zero.) Delta-time is in some 
      fraction of a beat (or a second, for recording a track with 
      SMPTE times), as specified in the header chunk.

      <event> = <MIDI event> | <sysex event> | <meta-event>
                                  <byte>          <byte>
        <MIDI event> is any MIDI channel message See 
        Appendix 1 - MIDI Messages. Running status is used: 
        status bytes of MIDI channel messages may be omitted if 
        the preceding event is a MIDI channel message with the 
        same status. The first event in each MTrk chunk must specify 
        status. Delta-time is not considered an event itself: it is 
        an integral part of the syntax for an MTrk event. Notice that 
        running status occurs across delta-times.

        <sysex event> is used to specify a MIDI system exclusive message, 
        either as one unit or in packets, or as an "escape" to specify 
        any arbitrary bytes to be transmitted. See Appendix 1 - MIDI 
        Messages. A normal complete system exclusive message is stored 
        in a MIDI File in this way: F0 <length> <bytes to be transmitted after F0>

        <meta-event> specifies non-MIDI information useful to this format 
        or to sequencers, with this syntax: FF <type> <length> <bytes>
        All meta-events begin with FF, then have an event type byte 
        (which is always less than 128), and then have the length of 
        the data stored as a variable-length quantity, and then the data 
        itself. If there is no data, the length is 0.

*/
    //writeToFile(  event,     pitch/note, velocity, getDelta      )
void writeToFile(byte eventType, byte b1, byte b2, int delta) {
  if (!file) return;
  writeVarLen(file, delta);
  file.write(eventType);
  file.write(b1);
  file.write(b2);
}

/**
   Encode a unsigned 32 bit integer as variable-length byte sequence
   of, at most, 4 7-bit-with-has-more bytes. This function is supplied
   as part of the MIDI file format specification.
*/
void writeVarLen(File file, unsigned long value) {                                                  /* -- http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html */
  // capture the first 7 bit block
  unsigned long buffer = value & 0x7f;

  // shift in 7 bit blocks with "has-more" bit from the
  // right for as long as `value` has more bits to encode.
  while ((value >>= 7) > 0) {
    buffer <<= 8;
    buffer |= HAS_MORE_BYTES;
    buffer |= value & 0x7f;
  }

  // Then unshift bytes one at a time for as long as the has-more bit is high.
  while (true) {
    file.write((byte)(buffer & 0xff));
    if (buffer & HAS_MORE_BYTES) {
      buffer >>= 8;
    } else {
      break;
    }
  }
}

Citations & Borrowed Code:
1. Pomax Midi Field Recorder
2. Adrian FSR Sensor Sketch
3. PRJC's GUI Tool
 
@HermesTheNurse:

I can confirm from my experience in developing the latest version of my TeensyMIDIPolySynth that the parameter lists are inconsistent between the two interface function lists. It's been some time since I dealt with this, so I don't remember specific functions where this is the case. I am certain that I did have to look at the .h file for each to confirm the order of the parameters when passing MIDI messages between these interfaces.

Hope that helps . . .

Mark J Culross
KD5RXT
 
Yes, sadly there is indeed quite a lot of inconsistency.

In the very early days of Arduino, around 2009, two MIDI libraries existed. MIDI.h by Francois Best was one of them with functions like sendNoteOn(), and the other seems to have been lost to time, but it had callbacks for receiving. Early on, people tended to use Francois Best's library for MIDI controllers which only transmit and the other library for projects which mostly receive.

By late 2009, Teensy started getting more USB protocols, including MIDI. From the very beginning, I wrote Teensy's USB MIDI to use the same functions and callbacks as BOTH of those established libraries. The order of parameters is indeed different in the function you call (originally MIDI.h by Francois Best) and the callback functions you create which the library calls when messages arrive.

Later Francois Best decided to support the callback functions in his MIDI.h. While inconsistent, he chose to support the parameter order as that other library (and by then Teensy's USB MIDI), so people who had already written code with the callbacks could maintain compatibility. His MIDI.h became the most widely used and that other library faded away (as far as I know) from the time when libraries tended to be published on wiki sites, before github became popular.

In 2012, Arduino made their first native USB board, Arduino Leonardo. When it came time to implement MIDI, sadly the Arduino developers rejecting using the MIDI functions long established by Francois Best in MIDI.h. Instead they made MIDIUSB.h, which nobody seems to like using.

It's so unfortunate we ended up with so many inconsistent USB MIDI functions. In hindsight, had I known back in 2010 how everything would turn out, I probably should have made callback functions consistent with Francois Best's MIDI.h library. But knowing only the info available at the time, at every step keeping compatibility with callback functions in programs people had already written seemed like the best decision. That's how we ended up here in 2023 with so much inconsistency.
 
Thank you @kd5rxt-mark, knowing that I’ll want to be comparing/translating the .h files is helpful!

And thank you as well @PaulStoffregen, the rundown on Arduino, MIDI libraries, and Teensy’s development of MIDI is helpful as well. I imagine it was difficult writing Teensy’s USB MIDI to straddle the common language of both preexisting libraries.

By late 2009, Teensy started getting more USB protocols, including MIDI. From the very beginning, I wrote Teensy's USB MIDI to use the same functions and callbacks as BOTH of those established libraries. The order of parameters is indeed different in the function you call (originally MIDI.h by Francois Best) and the callback functions you create which the library calls when messages arrive.

Is especially good to know, and clears up some doubts I had.

Thanks for explaining the point at which MIDIUSB.h came into play. I was wondering about that. Not to mention the other floating around, Arduino’s USB-MIDI.h library, which makes the whole shebang that much more fun to decipher.

Thank you both, I really appreciate the feedback and clarification!
 
Back
Top