Tuesday 28 January 2014

Arduino MIDI Synth No.2

Well, finally some more time this evening to continue the investigations and now have some basic MIDI handling and manipulation. Basically this tests that I can get MIDI callbacks and do something with them before passing onto the Real-Time MIDI on the VS1053.

Have followed Gordophone's lead again but instead of using the Ruin + Wesen MIDI library, I've used the library from fortyseveneffects. The class library description is here. I liked this library as it's only a set of midi.cpp and midi.h files that are quite well laid out and described and pretty easy to follow. Certainly gives me some comfort that if I need to jump in there and start twiddling with the code I've got a good basis to step on from.

BTW, for those of you on a Mac, adding libraries to Arduino installs requires finding the Arduino app, showing the package contents (in Finder click the cog wheel, select Show Package Contents) and then navigate to Contents/Resources/Java/libraries. I put the midi.cpp and midi.h into a new folder called Midi.

Here's the code... basically a reworking of Marmonizer v1 to read the MIDI input and transpose the note up or down an octave. The pot input is read with an analogRead, mapped to +/-12 and then the variable transposition is added to the incoming midiOn messages.

Note - ref to the MIDI numbers, note names and frequencies for me to find when I read this again. One octave is mapped to 12 semitones. In MIDI Middle C (C4 = 60).


///////////////////////////////////////////////////////////////////////
//
// HondrouMidifier
//
// Initial testing using Gordon Good's Marmonizer as a base
// http://gordophone.blogspot.co.uk/2010/01/marmonizer-v1.html
// Gordon Good (velo27 <at> yahoo <dot> com)
//
// Have used the MIDI library from FortySevenEffects instead of the Ruin+Wesen
// library.
// http://fortyseveneffects.github.io/arduino_midi_library/
// To prove some basic assumptions, the very first version is a simple MIDI 
// transposer. The input note is transposed up or down, and the amount of 
// transposition is controlled by a voltage applied to analog input 0, 
// e.g. with a potentiometer.
//
// This proves:
// - That we can do the transposition with reasonable latency
// - That we've got the Midi library working properly
//
// Note: this will probably leave dangling notes if the transposition is
// changed between a note on and the corresponding note off. The final
// code will have to account for user knob-twisting while playing, and
// make sure it turns off the right notes. Probably some sort of a map
// that relates a received note to all the note on messages it spawned.
//


#include <SoftwareSerial.h>
#include <Midi.h>

SoftwareSerial MusicSerial(2, 3); //Soft TX on 3, we don't use RX in this code

// defines for MIDI Shield components only
#define KNOB1  0
#define KNOB2  1

#define STAT1  7
#define STAT2  6

// Music shield pins - both digital
#define MUSIC_PIN_MIDI_IN  3 // soft serial TX -> VS1053 RX
#define MUSIC_PIN_RESET  4   // VS1053 RESET


int trPotPin = KNOB1; // Analog pin for reading the transposition potentiometer
int ledPin = STAT1;  // LED pin to blink for debugging

int transposition = 0; // number of semitones to transpose (negative = transpose down)

void noteOnCallback(byte channel, byte pitch, byte velocity) 
{  
  digitalWrite(ledPin, HIGH);

  noteOn(channel, pitch + transposition, velocity);
}

void noteOffCallback(byte channel, byte pitch, byte velocity) 
{
  digitalWrite(ledPin, LOW);
  
  noteOff(channel, pitch + transposition, velocity);  
}

/////////////////////////////////////////////////////////////////////////////
//  Setup routine             
/////////////////////////////////////////////////////////////////////////////
void setup() 
{
  pinMode(STAT1,OUTPUT);   
  pinMode(STAT2,OUTPUT);  
  
  // Initiate MIDI communications, listen to all channels
  MIDI.begin(MIDI_CHANNEL_OMNI);    
  
  // Connect the HandleNoteOn function to the library, so it is called upon reception of 
  // a NoteOn.
  MIDI.setHandleNoteOn(noteOnCallback);    
  MIDI.setHandleNoteOff(noteOffCallback);  

  pinMode(trPotPin, INPUT);
  digitalWrite(trPotPin, HIGH);
  
  // VS1053
  //start serial with midi baudrate 31250
  MusicSerial.begin(31250);      
  
  // Reset the VS1053
  pinMode(MUSIC_PIN_RESET, OUTPUT);
  digitalWrite(MUSIC_PIN_RESET, LOW);
  delay(100);
  digitalWrite(MUSIC_PIN_RESET, HIGH);
  delay(100);
  talkMIDI(0xB0, 0x07, 120); //0xB0 is channel message, set channel volume to near max (127)
  
  digitalWrite(STAT1,LOW);
  digitalWrite(STAT2,LOW);
}

/////////////////////////////////////////////////////////////////////////////
//  Main loop             
/////////////////////////////////////////////////////////////////////////////

void loop() 
{
    // Read the transposition pot, and map the value to a + or - one octave transposition
    transposition = map(analogRead(trPotPin), 0, 1023, -12, 12);
    
    // Call MIDI.read the fastest you can for real-time performance.
    MIDI.read();
  
    // There is no need to check if there are messages incoming if they are bound to a Callback 
    // function.
}

/////////////////////////////////////////////////////////////////////////////
//  Functions             
/////////////////////////////////////////////////////////////////////////////

//Send a MIDI note-on message.  Like pressing a piano key
//channel ranges from 0-15
void noteOn(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(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(STAT1,HIGH);

  MusicSerial.write(cmd);
  MusicSerial.write(data1);

  //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)
  {
    MusicSerial.write(data2);
  }
  digitalWrite(STAT1,LOW);
}

No comments:

Post a Comment