Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 5 of 5

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

  1. #1

    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);
    }
    Click image for larger version. 

Name:	Sketch(A)Breadboard-min.jpg 
Views:	11 
Size:	83.3 KB 
ID:	31990Click image for larger version. 

Name:	Sketch(B)Breadboard3-min.jpg 
Views:	10 
Size:	66.9 KB 
ID:	31989Click image for larger version. 

Name:	Sketch(C)BreadboardNotWorking2-min.jpg 
Views:	10 
Size:	176.9 KB 
ID:	31988

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

  2. #2
    Senior Member
    Join Date
    Apr 2020
    Location
    DFW area in Texas
    Posts
    659
    @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

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    28,465
    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.

  4. #4
    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.

    Quote Originally Posted by PaulStoffregen View Post
    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!

  5. #5

    Solved the problem here

    Solved the "writing standard-midi-file format to microSD-card" issue this thread had been created to solve. Not so much a case of library inequivalencies as just pointing parameters in the proper order. Details are here...

    https://forum.pjrc.com/threads/73542...811#post331811

    ...in case anyone's interested.

    Thank you again for your thorough explanations.

    - Colby

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •