Interfacing between MIDI files on an SD card and arduino mega,

So I am trying to get my arduino mega (2560) to act as the brain for a project involving midi files from an SD card being played by bells connected to motors on relays (one output signal per motor).

Eventually I want to be able to load up a bunch of midi files onto an SD card and then have each song played sequentially by my motors (which will then spin and generate the sounds for each "note").

I have a piece of test code that seems to be reading the output data from the midi file and sending it just fine to the serial monitor (as seen here),


//include appropriate libraries 
#include <MIDI.h>
#include <SPI.h>
#include <SD.h>

//create "filename" placeholder variable for files when opened
File myfile;

//SD pin designations FOR ARDUINO UNO are as follows: MOSI-11, MISO-12, SCK-13, CS-10. SD pin designations FOR ARDUINO MEGA are as follows:MOSI-51, MISO-50, SCK-52, CS-53
const int chipSelect = 53; 

// put your setup code here, to run once:
void setup() {

  //CS pin must be left as an output or else SD library functions will not work.
  pinMode(chipSelect, OUTPUT);

  //start serial monitor (for testing purposes only) 
  Serial.begin(9600);

  // wait for Serial Monitor to connect. Needed for native USB port boards only:
  while (!Serial);
  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("1. is a card inserted?");
    Serial.println("2. is your wiring correct?");
    Serial.println("3. did you change the chipSelect pin to match your shield or module?");
    Serial.println("Note: press reset or reopen this serial monitor after fixing your issue!");
    while (true);
  }
  Serial.println("initialization done.");
 
  }


// put your main code here, to run repeatedly:
void loop() {
  
    myfile = SD.open("1.mid"); //open file "1.mid" is just the name of the first file on the SD card, eventually I want it to go through a list of file names. 
    if (myfile){
      Serial.println("1.mid");
      while (myfile.available()) {
        Serial.println(myfile.read()); //read file in serial monitor format
        }
    myfile.close(); //close file
      }
    
}

But when I tweak a couple things and try to put it all together in my actual code, as seen here, I have problems reading the SD card and it doesn't even make it past the initialization stage.




//include appropriate libraries 
#include <MIDI.h>
#include <SPI.h>
#include <SD.h>

//create "filename" placeholder variable for files when opened
File songname;

//create all motor outputs (relay signals (HIGH/LOW))
int NoteA = 2;
int NoteB = 3;
int NoteC = 4;
int NoteD = 5;
int NoteE = 6;
int NoteF = 7;
int NoteG = 8;

//SD pin designations are as follows: MOSI-51, MISO-50, SCK-52, CS-53
const int chipSelect = 53; 
//set indexing variable for files
int i = 0; 

//MIDI pitch values for each "note"
int Aa [ ] = {0, 1, 12, 13, 24, 25, 36, 37, 48, 49, 60, 61, 72, 73, 84, 85, 96, 97, 108, 109, 120, 121}; //22 items in list
int Bb [ ] = {2, 2, 14, 14, 26, 26, 38, 38, 50, 50, 62, 62, 74, 74, 86, 86, 98, 98, 110, 110, 122, 122}; 
int Cc [ ] = {3, 4, 15, 16, 27, 28, 39, 40, 51, 52, 63, 64, 75, 76, 87, 88, 99, 100, 111, 112, 123, 124};
int Dd [ ] = {5, 6, 17, 18, 29, 30, 41, 42, 53, 54, 65, 66, 77, 78, 89, 90, 101, 102, 113, 114, 125, 126};
int Ee [ ] = {7, 7, 19, 19, 31, 31, 43, 43, 55, 55, 67, 67, 79, 79, 91, 91, 103, 103, 115, 115, 127, 127};
int Ff [ ] = {8,  9, 20, 21, 32, 33, 44, 45, 56, 57, 68, 69, 80, 81, 92, 93, 104, 105, 116, 117, 128, 129};
int Gg [ ] = {10, 11, 22, 23, 34, 35, 46, 47, 58, 59, 70, 71, 82, 83, 94, 95, 106, 107, 118, 119, 130, 131};
  
//create a new array with all song names in a list form (for indexing) ---eventually I will make this a list of all the filenames on SD card
const char* songList [ ] = {"1.mid", "2.mid"}; 
int songTotal = 1; //number of items in songlist/on SD card

// Create and bind the MIDI interface to the default hardware Serial port
MIDI_CREATE_DEFAULT_INSTANCE();



//////////////////////////////////////////~code~//////////////////////////////////////////////////////////////


// put your setup code here, to run once:
void setup() {

  //start serial monitor (for testing purposes only) 
  Serial.begin(38400);

  //set output signals for each "note"(motor) 
  pinMode(NoteA, OUTPUT);
  pinMode(NoteB, OUTPUT);
  pinMode(NoteC, OUTPUT);
  pinMode(NoteD, OUTPUT);
  pinMode(NoteE, OUTPUT);
  pinMode(NoteF, OUTPUT);
  pinMode(NoteG, OUTPUT); 
  
  //CS pin must be left as an output or else SD library functions will not work.
  pinMode(chipSelect, OUTPUT);

  // wait for Serial Monitor to connect. Needed for native USB port boards only:
  while (!Serial);
  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("1. is a card inserted?");
    Serial.println("2. is your wiring correct?");
    Serial.println("3. did you change the chipSelect pin to match your shield or module?");
    Serial.println("Note: press reset or reopen this serial monitor after fixing your issue!");
    while (true);  
  }
  Serial.println("initialization done.");

  //set MIDI interfacing to reference the callback functions below
  MIDI.begin();
  MIDI.setHandleNoteOn(doSomeStuffWithNoteOn);
  MIDI.setHandleNoteOff(doSomeStuffWithNoteOff);
  
  
  }

/////////////////////////////////////////////////~main run~////////////////////////////////////////////////

// put your main code here, to run repeatedly:
void loop() {
    for (int i = 0; i<=songTotal; i++){
    songname = SD.open(songList[i]); //open file - should be smth like this: "songnamesarray[i]"
    if (songname){
      Serial.println(songList[i]);
      while (songname.available()) {
        MIDI.read(); //read file in MIDI format to reference callback functions below
        }
    songname.close(); //close file
      }
    }
    i=0;
}


////////////////////////////////////~callback functions~/////////////////////////////////////////////////


//assign actions for notes using a callback
void doSomeStuffWithNoteOn(byte channel, byte pitch, byte velocity){
  //if pitch is within the arrays above, turn on the corresponding motor
  for (int count = 0; count <= 21; count++){
    if(pitch == Aa[count]){
      digitalWrite(NoteA, HIGH); 
      }
    if(pitch == Bb[count]){
      digitalWrite(NoteB, HIGH); 
      }
    if(pitch == Cc[count]){
      digitalWrite(NoteC, HIGH); 
      }
    if(pitch == Dd[count]){
      digitalWrite(NoteD, HIGH); 
      }
    if(pitch == Ee[count]){
      digitalWrite(NoteE, HIGH); 
      }
    if(pitch == Ff[count]){
      digitalWrite(NoteF, HIGH); 
      }    
    if(pitch == Gg[count]){
      digitalWrite(NoteG, HIGH); 
      }
    else {
      }  
    }
    }
  

void doSomeStuffWithNoteOff(byte channel, byte pitch, byte velocity){
  //if pitch is within the arrays above, turn off the corresponding motor
  for (int count = 0; count <= 21; count++){
    if(pitch == Aa[count]){
      digitalWrite(NoteA, LOW); 
      }
    if(pitch == Bb[count]){
      digitalWrite(NoteB, LOW); 
      }
    if(pitch == Cc[count]){
      digitalWrite(NoteC, LOW); 
      }
    if(pitch == Dd[count]){
      digitalWrite(NoteD, LOW); 
      }
    if(pitch == Ee[count]){
      digitalWrite(NoteE, LOW); 
      }
    if(pitch == Ff[count]){
      digitalWrite(NoteF, LOW); 
      }    
    if(pitch == Gg[count]){
      digitalWrite(NoteG, LOW); 
      }
    else {
      } 
    }
}


This code doesn't even seem to make it past the initialization stage. My serial monitor says "Initializing SD card..." and then random characters, and freezes. My main problem right now, and the one I really need help with, is that I cannot get the SD card to be recognized once I drop it into my main code.

Needless to say, no files are being read, and thus no motors are spinning.

PS: just had this thought as I was writing this, but could i take the data directly from my test code and parse it out into usable data without having to use the midi library? From what I have seen from my serial monitor using test code, the data from the midi file seems to just be integers, which could still be compatible with my "official" code, as long as I separate the pitch data from the channel and velocity data....I am just unsure how to do that either haha.

Advice and insight very appreciated! Thanks!

How does the MIDI library know you want it to read from the file named 'songname'?

I do not know. If you have a suggestion please share! I thought it would be ok because it was nested inside that while (songname.available) loop, but if that is not the case please enlighten me. Would this also relate to my initialization problem?

As far as I can tell, the MIDI library has no provision for reading MIDI files.

I don't think the MIDI library does anything from an SD card (last time I looked).

You may be thinking of the MD_MIDIFile library that reads MIDI files on an SD card for playback. Install from the Library Manager. MIDI files are a specific format that includes timing parameters and need to be played back in a specific manner (ie, not just open and read bytes).

1 Like

Thanks for the insight on that part guys I truly appreciate it. I will check out the MD_MIDIFile library for sure! However, would fixing this issue also fix my SD card initialization issue? I am still confused as to why i was reading the files just fine with my test code but I couldn't even get it past the initializing stage once I incorporated it into my full code.

Not the cause of your problem but for (int count = 0; count < 23; count++) and for (int count = 0; count <= 22; count++) are wrong; your array Aa has 22 elements and they are numbered from 0 to 21; I'm too lazy to count the other arrays.

1 Like

This would be a problem if you ever actually used Pin 1 for output. Pin 1 is one of the two Serial pins on the UNO, NANO, and MEGA.

Why are you using NoteA-G as pin numbers in setup() and using them as variables elsewhere?

1 Like

Gotcha thanks, just switched my pins to digital 2-8 instead of 1-7. In regards to your question, thanks for bringing that up because I forgot digitalWrite is the function for that command, thanks for that heads up I will fix immediately. But again, would this help with my initialization problem?

I recommend you define pin names with:
const byte NoteAPin = 2;
instead of:
int NoteAPin = 2;

This ('const') will tell the compiler that you promise not to change the value of NoteAPin. Then the compiler can tell you that this line is trying to break your promise:
NoteAPin = HIGH;

Many programmers still use the ancient pre-processor macro definition style of constants:
#define NoteAPin 2
This will ALSO cause a compile error when you write "NoteAPin = HIGH;" but it will be a more cryptic error because what the compiler sees is:
'2 = HIGH;' (or '2 = 1;' if HIGH is also defined as a macro).

Great thanks I will incorporate this moving forward. Any thoughts about my initialization errors though?

Nothing I saw in the software. It looks like your board is resetting before the full message can be sent. Maybe one of the SD Card pins is shorted to power or ground? That might cause a power dip enough to reset the board.

Ok thanks anyways! Here are a couple more pictures in case they provide any more insight.


That doesn't look like the proper way to power a motor.

Eventually I will be using relays to turn on larger motors that will be powered externally. The single small motor is for testing purposes only.

Still, you should not power it directly from an Arduino pin, but through transistor at least. There doesn't appear to be a fly-back diode, and there really should be.
Even powering the motor through USB to begin with is questionable. What are the specs of the motor ?

Also, Are you sure that the SD card adapter uses 5V supply. Maybe you adapter has it all built in, but most use a 3V supply and the signals lines also need to have a level conversion buffer inserted between the Arduino and the SD card adapter.

1 Like

Hey thanks for that! This could actually be a solution to my problem...I will try integrating a signal level converter (to increase the signal input levels right?) between the SD adapter and Arduino and also connect the SD shield to the 3.3v pin instead of 5v. Also, it seems like I should have been using the MD_MIDIFile library the whole time instead of just the MIDI library, so I definitely have some work to do//tests to run before I ask more questions. Also if anyone has links that could help with integrating the MD_MIDIFile library into a system similar to the one I am designing please drop them here!

What level conversion buffer would you recommend?

to increase the signal input levels right

To convert between 5V and 3V signal levels. It changes the max voltage for the signal.

Lots of info out there about interfacing between 5V and 3V systems. Can be done in a lot of ways. Do a search and pick one that suits your circumstances.