Simultaneous MIDI Input/Output: "MIDI.sendNoteOn" and "void handleNoteOn(byte channel, byte pitch, byte velocity)"

Hi everyone,

Apologies in advance if I violate any forum conventions. I've tried to distill the issue down to only the relevant code, but I understand it is still a mouthful (to a student like me at least). If I do violate forum rules, I will be sure to take note and adapt moving forward😅

Here are my hardware/software specifications:

  • Teensy 4.1
  • Rev D2 Audio Shield w/ microSD card
  • (x4) FSR sensors
  • (x2) Female 5-pin DIN midi sockets
  • Arduino IDE Version 2.2.0

Here is the context and problem:

  • Sketch(A) is successfully saving ".mid" files to a microSD card when supplying MIDI keyboard input via 5-pin DIN (MIDI-IN input --> serial pinRX1 --> .mid file @ microSD card output.

  • Sketch(B) is successfully outputting usb MIDI messages to my DAW from four FSR sensors each connected to their respective analog pins on the Teensy 4.1 micro-controller.

  • In appending the newer code (Sketch(B)) to my original (Sketch(A)), I've been unable to store MIDI input from analog pin sensors to .mid files. This unsuccessful hybrid attempt is illustrated in the attached Sketch(C).

This is my end goal in terms of signal flow:
(x4) FSR Sensor input --> (x4) Analog pins --> usb MIDI + Audio + .mid file @ microSD card output

Here is my question:
Why isn't the information contained within INPUT commands like "MIDI.sendNoteOn (Note[i], 0, 10);" and "MIDI.sendNoteOff (Note[i], 0, 10);" being recognized by the independent OUTPUT <MIDI.h> "setHandle..." callback functions, such as "void handleNoteOn(byte channel, byte pitch, byte velocity) {...}" and "void handleNoteOff(byte channel, byte pitch, byte velocity) {...}"? Perhaps the solution is right in front of my face, but I’ve opened the fridge, and I can’t find the 2% milk😶‍🌫️

And here is my simplified code:

// File and MIDI handling
#include <SD.h>
#include <MIDI.h>
// Other Libraries
#include <Bounce.h> 
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// Sketch(B) MIDI-related functions used later, declared here.
void NoteOnSend (int);      
void PolyTouchSend (int);    

// INDEX # -->  0     1     2    3
int FSRpin[] = {A17, A16, A15, A14};                                   
const int FSRs = 4;                                 
int Note [] = {60, 61, 62, 63};       
int counter [FSRs];                   
int VELMASK = 0;
int ATMASK = 0;
int AFTERTHRESH = 50;         
int THRESH = 45;   
int VELTIME = 500;      
int AFTERTIME = 2500; 
int MIDIMIN = 20;

#include <RTClib.h>
RTC_DS3231 RTC;
bool HAS_RTC = false;

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

// Macros to be included in .mid file parameters
#define NOTE_OFF_EVENT 0x80
#define NOTE_ON_EVENT 0x90
#define CONTROL_CHANGE_EVENT 0xB0
#define PITCH_BEND_EVENT 0xE0
// Added to match the other <MIDI.h> "setHandle..." callback functions of Sketch(A)
#define AFTER_TOUCH_POLY_EVENT 0xA0

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

// Interval at which file's in-memory content is flushed to disk every 400ms
#define FILE_FLUSH_INTERVAL 400
String filename;
File file;

MIDI_CREATE_DEFAULT_INSTANCE();

//---------------------------------------------------------------------------
void setup() {
  // Baud rate
  Serial.begin (32500);               // Sketch(B) works with or without setting BAUD rate

  // Set up MIDI handling
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandlePitchBend(handlePitchBend);
  MIDI.setHandleControlChange(handleControlChange);
  // Added to match the other <MIDI.h> "setHandle..." callback functions of Sketch(A)
  MIDI.setHandleAfterTouchPoly(handleAfterTouchPoly);

  // Set up RTC interfacing
  if (RTC.begin()) {
    SdFile::dateTimeCallback(dateTime);
    HAS_RTC = true;
  }

  // Set up SD card functionality and allocate a file
  pinMode(CHIP_SELECT, OUTPUT);
  if (SD.begin(CHIP_SELECT)) {
    creatNextFile();
    if (file) {
      writeMidiPreamble();
    }
  }
}

//---------------------------------------------------------------------------
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());
}

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

//---------------------------------------------------------------------------
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);       // 14 Bytes

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

  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);         // 7 Bytes
}

//---------------------------------------------------------------------------
void loop() {

  // File flushing and handling MIDI input
  updateFile();
  MIDI.read();

  // Sketch(B) - Iterating through analog pins 
  for (int i = 0; i < FSRs; i++) {
    int FSRRead = analogRead(FSRpin[i]);
    if (FSRRead > THRESH) {
      counter[i] ++;
      if (!(VELMASK & (1 << i)) && (counter[i] == VELTIME)) {
        VELMASK |= (1 << i);                   
        counter [i] = 0;
        NoteOnSend (i); 
        }
      if (counter [i] == AFTERTIME) {
        counter [i] = 0;
        PolyTouchSend(i);
      }
    }
    else {
      if (VELMASK & (1 << i)) {          
        usbMIDI.sendNoteOff (Note[i], 0, 10);
        // Added in attempt to send data through serial MIDI (<MIDI.h>)
        MIDI.sendNoteOff (Note[i], 0, 10);
        VELMASK &= ~ (1 << i);                 
        counter [i] = 0;
      }
    }
  }
}

//---------------------------------------------------------------------------
void updateFile() {
  loopCounter = millis();
  if (loopCounter - lastLoopCounter > FILE_FLUSH_INTERVAL) {
    checkReset();
    lastLoopCounter = loopCounter;
    file.flush();
  }
}

//---------------------------------------------------------------------------
void NoteOnSend (int j) {
  int FSRRead = analogRead(FSRpin [j]);                     
  int velocity = map (FSRRead, 0, 800, MIDIMIN, 127);
  usbMIDI.sendNoteOn (Note[j], velocity, 10);
  // Added in attempt to send data through serial MIDI (<MIDI.h>)
  MIDI.sendNoteOn (Note[j], velocity, 10)
}

//---------------------------------------------------------------------------
void PolyTouchSend (int j) {                                     
  int FSRRead = analogRead(FSRpin [j]);
  if (FSRRead > AFTERTHRESH) {
    int aftertouch = map (FSRRead, 0, 800, MIDIMIN, 127);
    usbMIDI.sendPolyPressure (Note[j], aftertouch, 10);
    // Added in attempt to send data through serial MIDI (<MIDI.h>)
    MIDI.sendPolyPressure (Note[j], aftertouch, 10);
  }
}

//---------------------------------------------------------------------------
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());
}
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());
}

// Added to incorporate PolyAfterTouch in <MIDI.h> "setHandle..." callbacks
void handleAfterTouchPoly(byte channel, byte pitch, byte pressure);
  writeToFile(AFTER_TOUCH_POLY_EVENT, pitch, pressure, getDelta());

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

//---------------------------------------------------------------------------
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);
}

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

I realize that's a lot of information to parse through, and if anyone has taken the time to glance at it, thank you. I don't expect to be spoon-fed here, but have spent a few days chewing on this issue searching for a solution. Although the problem is rather niche, perhaps there are others who may benefit from this example in the future!:peace_symbol:

Is a single micro-controller capable of translating analog pin input to MIDI data AND outputting that data in a Standard Midi Protocol stream? I've read through the Mcgill specifications several times now, but have a feeling this issue has to do with the simultaneous I/O's of the <MIDI.h> library...

Attachments:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.