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