Pages: 1 [2]   Go Down
Author Topic: Read and Play MIDI file in arduino  (Read 8809 times)
0 Members and 1 Guest are viewing this topic.
San Francisco
Offline Offline
Newbie
*
Karma: 0
Posts: 23
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Neat project! I'm trying to accomplish something similar. Specifically, I'd like the Arduino to output midi signals to my SparkFun Music Instrument Shield http://www.sparkfun.com/products/10587. Ideally, I'd like to combine some "canned" midi signals (including a variety of different drum loops) with some "live" signals generated from sensors. I had some success using the NewSoftSerial library to feed MIDI data to the shield.

Here's a sketch I did (based on one of the Sparkfun example sketches) which plays a simple beat. It reads the notes from characters in an array ("b" is kick, "t" is hi-hat, "k" is snare, space is a rest). It reads the delays attached to each note from another array, which in this case I've set up to allow swing on the 16ths.
Code:

#include <NewSoftSerial.h>
NewSoftSerial mySerial(2, 3); //Soft TX on 3, we don't use RX in this code

byte note = 0; //The MIDI note value to be played
byte resetMIDI = 4; //Tied to VS1053 Reset line
byte ledPin = 13; //MIDI traffic inidicator
int instrument = 0;


int length;
char beat1[] = { "bbttktttbktbkttt"};  //Beat pattern #1
char beat2[] = { "bttbb   b  bb   "};    //Beat pattern #2
bool tempoUp = 1;
float swing = 0.33;                           //0 is no swing; 0.33 should approximate typical "triplet" swing (2/3 + 1/3)
float beats[] = { 1+swing, 1-swing, 1+swing, 1-swing, 1+swing, 1-swing, 1+swing, 1-swing,
  1+swing, 1-swing, 1+swing, 1-swing, 1+swing, 1-swing, 1+swing, 1-swing }; 

float tempo = 60;

void playNote(char note, float duration) {
  char names[] = { 'b', 't', 'k' };
  int tones[] = { 35, 42, 38 };

  for (int i = 0; i < 3; i++) {
    if (names[i] == note) {
      Serial.print(note);
      noteOn(0,tones[i],100);
      // 16th note in ms         1 beat             1 min               60000 ms   
      //  = 15000/tempo        ------------      x -------          x  ----------
      //                               4 16ths notes     beats (bpm)     min         
      delay(duration*15000/tempo);  //duration adjustment allows for swing
      noteOff(0,tones[i],0);
    }
  }
}

void playLoop(char beat[]) {
  Serial.print("Swing:");
  Serial.print(float(swing));
  Serial.print(" ");
 
  Serial.print("Tempo:");
  Serial.print(int(tempo));
  Serial.print(" ");
  for (int i = 0; i < 16; i++) {
    if (beat[i] == ' ') {
      Serial.print("-");
      delay(beats[i]*15000/tempo);  //See above; each 16th's length in ms is 15000/tempo
    } else {
      playNote(beat[i], beats[i]);
    }
    delay(0);
    if (tempoUp) { tempo *= 1.005; }  //accelerando...
    else { tempo /= 1.005; }          //or decelerando
    if (tempo > 150) { tempoUp = 0; } //slow down after 150 bpm
    if (tempo < 50) { tempoUp = 1; }  //speed up after 50 bpm
  }
  Serial.println("");
}


void setup() {
  Serial.begin(57600);
  //Setup soft serial for MIDI control
  mySerial.begin(31250);

  //Reset the VS1053
  pinMode(resetMIDI, OUTPUT);
  digitalWrite(resetMIDI, LOW);
  delay(100);
  digitalWrite(resetMIDI, HIGH);
  delay(100);
}

void loop() {
  talkMIDI(0xB0, 0x07, 50);      //0xB0 is channel message, set channel volume to near max (127)
  talkMIDI(0xB0, 0, 0x78);       //Bank select drums
  talkMIDI(0xC0, instrument, 0); //Select instrument; doesn't matter which for drums
 
  playLoop(beat1);
  playLoop(beat2);
 
  for (int i = 0; i < 16; i++) {  //Set swing for each pair in beats[]
    if (i%2 == 0) { beats[i] = 1+swing; }
    else { beats[i] = 1-swing; }
  }
}


void noteOn(byte channel, byte note, byte attack_velocity) {
  talkMIDI( (0x90 | channel), note, attack_velocity);
}

void noteOff(byte channel, byte note, byte release_velocity) {
  talkMIDI( (0x80 | channel), note, release_velocity);
}

//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127
void talkMIDI(byte cmd, byte data1, byte data2) {
  digitalWrite(ledPin, HIGH);
//  Serial.write(cmd);
//  Serial.write(data1);
  mySerial.print(cmd, BYTE);
  mySerial.print(data1, BYTE);
  if( (cmd & 0xF0) <= 0xB0 || (cmd & 0xF0) == 0xE0)
//     Serial.write(data2);
  mySerial.print(data2, BYTE);
  digitalWrite(ledPin, LOW);
}



To be continued in next post....
Logged

San Francisco
Offline Offline
Newbie
*
Karma: 0
Posts: 23
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

[Continuation of my prior post]
That seems to work well enough, but one thing I'd really like to improve is the "feel" of the beat and the ability to plug in more complicated sequences. As currently set up, each beat is locked in a static "grid" and I can't easily add additional subdivisions (such as 32nd notes or triplets), or play notes longer than a 16th. Also, the timing might be a bit off since calculations happen between each delay. I can't tell.

So, for my next step I'd like to play a MIDI file, or perhaps a sanitized, simplified version thereof, using a timer instead of delays. I've seen examples of how to accomplish a single task using a timer, but I get confused when trying to apply to a long list of events. In pseudo-code, I'd like to:

0. Load 2D array with a sequence of { int tick, int note, int velocity } lines, with a noteOn and noteOff for each note
1. Loop through the array as a timer counts ticks for each measure.
2. With each array item, check if the tick has passed. If so, trigger the noteOn. If not, wait a bit (1ms?) and check that point in the array again. (I think that triggering within about 10ms of the desired time will be good enough for me.)
3. If the timer count hits 384 (384 = 4 beats x 4 subdivisions x 24 ticks; I think that's a tenth as many as the MIDI file I was working from), reset it to zero and go back to step 1.

Is that a sensible way to set it up? How could I optimize it better to allow more cycles for reading and interpreting input?

I've given a first shot at coding this, but I'm getting errors relating to pointers (which I'm trying to learn about) in the loop() section where I look up the note in the array (2nd field) based on the tick in the array (1st field). Should I be setting this up differently, or would it be a simple syntax tweak to fix this?

Code:
#include <NewSoftSerial.h>
NewSoftSerial mySerial(2, 3); //Soft TX on 3, we don't use RX in this code

byte note = 0; //The MIDI note value to be played
byte resetMIDI = 4; //Tied to VS1053 Reset line
byte ledPin = 13; //MIDI traffic indicator
int instrument = 0;
int scale_major[] = {0, 2, 4, 5, 7, 9, 11};        //one octave of major scale
int scale_minpent[] = {0, 2, 3, 5, 6, 7, 10};     //minor pentatonic scale
int scale_consonant[] = {0, 2, 4, 7, 9, 12, 14}; //major pentatonic scale plus octave and 9th (to be same array length as others)


int drum_motown[100][3] = //Tick (out of 384 per bar), note, velocity
{                         //This is an attempt to adapt data from an actual MIDI file, with
  { 0,54,94 },            //matching 0-velocity lines in place of noteOff messages
  { 0,48,97 },
  { 8,54,0 },
  { 48,54,75 },
  { 51,48,0 },
  { 54,54,0 },
  { 96,50,106 },
  { 96,54,96 },
  { 103,54,0 },
  { 127,50,0 },
  { 144,54,73 },
  { 153,54,0 },
  { 171,50,92 },
  { 177,50,0 },
  { 192,54,82 },
  { 201,54,0 },
  { 219,48,85 },
  { 240,54,84 },
  { 240,48,0 },
  { 246,54,0 },
  { 267,48,86 },
  { 273,48,0 },
  { 288,54,101 },
  { 288,50,103 },
  { 296,54,0 },
  { 318,50,0 },
  { 336,54,77 },
  { 344,54,0 },
  { 363,50,70 },
  { 368,50,0 }
};
int drumSpot = 0;
int drumSpotTime = 0;

long curTime = 0;
int timeclock96 = 0;  //96 ticks per beat, based on 4 subdivs per beat and 24 ticks per subdiv (not 240 like the source midi file)




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

  //Setup soft serial for MIDI control
  mySerial.begin(31250);

  //Reset the VS1053
  pinMode(resetMIDI, OUTPUT);
  digitalWrite(resetMIDI, LOW);
  delay(100);
  digitalWrite(resetMIDI, HIGH);
  delay(100);
 
  talkMIDI(0xB0, 0x07, 30); //0xB0 is channel message for channel 1 (0), set channel volume to 30
  talkMIDI(0xB0, 0, 0x00); //Bank select GM1
 
  talkMIDI(0xB9, 0x07, 50); //0xB0 is channel message for channel 10 (9), set channel volume to 50
  talkMIDI(0xB9, 0, 0x78); //Bank select drums
 
  curTime = millis();
}




void loop() {
  timeclock96 = (millis() - curTime)/96;
  Serial.println(timeclock96);
  for (int j = drumSpot; j<100; j++) {
    drumSpotTime = (int)drum_motown[drumSpot,0];
    if (drumSpotTime < timeclock96) {
      noteOn(0, (byte)10, drum_motown[drumSpot,1], drum_motown[drumSpot,2]);   //ERROR HERE: invalid conversion from int* to byte
    }
  if (timeclock96 >= 96) {
    curTime = millis();
  }
 
//  playNoodling(0, random(48,59), scale_consonant, 180, 0);

}


void playNoodling(byte channel, int key, int scale[], float tempo, int instrument) {
  Serial.print(" Instrument: ");
  Serial.println(instrument, DEC);
  talkMIDI(0xC0, instrument, 0); //Set instrument number. 0xC0 is a 1 data byte command
  talkMIDI(0xCA, 30, 0); //Set instrument number. 0xC0 is a 1 data byte command
 
  for (int i = 0; i < 16; i++) {
    int note = key + scale[random(0, 6)];
    noteOn(1, channel, note, 80);
    noteOn(0, 10, drum_pattern1[i], 50);
   
    //1 minute      60 seconds    1000 ms               
    //---------  x  ---------  x  --------
    //120 Beats     1 minute      1 second
    delay(60000/tempo);
    noteOff(1, channel, note, 0);
    noteOff(0, 10, drum_pattern1[i], 0);
  }
}



void allOff(byte channel) {
  talkMIDI ( (0xB0 | channel), 123, 0);
}

//Send a MIDI note-on message.  Like pressing a piano key
//channel ranges from 0-15
void noteOn(boolean instr, byte channel, byte note, byte attack_velocity) {
  talkMIDI( (0x90 | channel), note, attack_velocity);
}

//Send a MIDI note-off message.  Like releasing a piano key
void noteOff(boolean instr, byte channel, byte note, byte release_velocity) {
  talkMIDI( (0x80 | channel), note, release_velocity);
}

//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127
void talkMIDI(byte cmd, byte data1, byte data2) {
  digitalWrite(ledPin, HIGH);
//  Serial.write(cmd);
//  Serial.write(data1);
  mySerial.print(cmd, BYTE);
  mySerial.print(data1, BYTE);

  //Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes
  //(sort of: http://253.ccarh.org/handout/midiprotocol/)
  if( (cmd & 0xF0) <= 0xB0)
//     Serial.write(data2);
   mySerial.print(data2, BYTE);

  digitalWrite(ledPin, LOW);
}



Many thanks in advance to anyone who can provide a suggestion as to how to improve this code! My apologies that it's so scrappy!
Logged

Pages: 1 [2]   Go Up
Jump to: