[Home] [Downloads] [Search] [Help/forum]

Gammon Forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  MIDI decoder
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

MIDI decoder

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (21,610 posts)  [Biography] bio   Forum Administrator
Date Thu 05 Feb 2015 05:03 AM (UTC)
Message
I posted this a while ago on the Arduino forum, and thought I should probably document it here as well, because it will have fallen into the "old posts" list by now.

MIDI hardware


MIDI interfaces (excepting the USB ones) use serial data at 31250 baud. The Arduino can easily send and receive at that rate, even using SoftwareSerial.

Electrically, MIDI is current (not voltage) driven, so to read it you need to use an opto-coupler (see http://www.midi.org/ for details). In particular http://www.midi.org/techspecs/electrispec.php shows details of the opto-coupler connections.

My own circuit was:




The resistors on the opto-coupler are slightly different based on discussions in the Arduino forum.

MIDI messages


See the MIDI site for details about the format of MIDI messages. MIDI messages basically start with a status (command) byte which has the high-order bit set (ie. 0x80 to 0xFF).

They are typically followed by one or two more bytes with the high-order bit clear.

For example, "Channel 1 note on" is 0x90 followed by the note number and velocity.

Channel-oriented commands have the channel number in the low-order bits. So for example, 0x91 is "Channel 2 note on", 0x92 is "Channel 3 note on" and so on.

Running Status


To save space, if you are sending the same command multiple times, it can be omitted.

For example:


0x90 0x01 0x02 0x08 0x09


That is "Channel 1 note on" (0x90) followed by note 1, velocity 2, and then note 8, velocity 9. This lets you send a chord without having to repeat the "note on" command for each note in the chord.

Also, to lever off this, if you send a "note on" with a velocity of zero, that is considered to be the same as "note off" so it is possible to turn notes on and off without changing the command (status) byte.


Code to decode MIDI



//  MIDI_decoder
//  
//  Author:  Nick Gammon
//  Date:    11th April 2012.
//  Version: 1.2
//  Released into the public domain.

//  Version 1.1: Amended to allow for repeated parameters to the same channel message
//    eg. 0x90 0x45 0x46 0x47 0x48 does a "note on" for two notes
//  Version 1.2: Removed "repeating" for system messages.
//  Version 1.3: Amended to allow for RealTime messages to be ignored

#include <SoftwareSerial.h>
#include <Streaming.h>

// Plug MIDI into pin D2 (MIDI in serial)

SoftwareSerial midi (2, 3);  // Rx, Tx

#define MESSAGE(which, desc) const char control_ ## which [] PROGMEM = desc; 
  
MESSAGE (0, "Bank Select (MSB)");
MESSAGE (1, "Modulation Wheel or Lever (MSB)");
MESSAGE (2, "Breath Controller (MSB)");
MESSAGE (3, "Undefined (MSB)");
MESSAGE (4, "Foot Controller (MSB)");
MESSAGE (5, "Portamento Time (MSB)");
MESSAGE (6, "Data Entry (MSB) (MSB)");
MESSAGE (7, "Channel Volume (formerly Main Volume) (MSB)");
MESSAGE (8, "Balance (MSB)");
MESSAGE (9, "Undefined:9 (MSB)");
MESSAGE (10, "Pan (MSB)");
MESSAGE (11, "Expression Controller (MSB)");
MESSAGE (12, "Effect Control 1 (MSB)");
MESSAGE (13, "Effect Control 2 (MSB)");
MESSAGE (14, "Undefined (MSB)");
MESSAGE (15, "Undefined (MSB)");
MESSAGE (16, "General Purpose Controller 1 (MSB)");
MESSAGE (17, "General Purpose Controller 2 (MSB)");
MESSAGE (18, "General Purpose Controller 3 (MSB)");
MESSAGE (19, "General Purpose Controller 4 (MSB)");
MESSAGE (20, "Undefined:20 (MSB)");
MESSAGE (21, "Undefined:21 (MSB)");
MESSAGE (22, "Undefined:22 (MSB)");
MESSAGE (23, "Undefined:23 (MSB)");
MESSAGE (24, "Undefined:24 (MSB)");
MESSAGE (25, "Undefined:25 (MSB)");
MESSAGE (26, "Undefined:26 (MSB)");
MESSAGE (27, "Undefined:27 (MSB)");
MESSAGE (28, "Undefined:28 (MSB)");
MESSAGE (29, "Undefined:29 (MSB)");
MESSAGE (30, "Undefined:30 (MSB)");
MESSAGE (31, "Undefined:31 (MSB)");
MESSAGE (32, "Bank Select (LSB)");
MESSAGE (33, "Modulation Wheel or Lever (LSB)");
MESSAGE (34, "Breath Controller (LSB)");
MESSAGE (35, "Undefined:35 (LSB)");
MESSAGE (36, "Foot Controller (LSB)");
MESSAGE (37, "Portamento Time (LSB)");
MESSAGE (38, "Data Entry (LSB)");
MESSAGE (39, "Channel Volume, formerly Main Volume (LSB)");
MESSAGE (40, "Balance (LSB)");
MESSAGE (41, "Undefined:41 (LSB)");
MESSAGE (42, "Pan (LSB)");
MESSAGE (43, "Expression Controller (LSB)");
MESSAGE (44, "Effect control 1 (LSB)");
MESSAGE (45, "Effect control 2 (LSB)");
MESSAGE (46, "Undefined:46 (LSB)");
MESSAGE (47, "Undefined:47 (LSB)");
MESSAGE (48, "General Purpose Controller 1 (LSB)");
MESSAGE (49, "General Purpose Controller 2 (LSB)");
MESSAGE (50, "General Purpose Controller 3 (LSB)");
MESSAGE (51, "General Purpose Controller 4 (LSB)");
MESSAGE (52, "Undefined:52 (LSB)");
MESSAGE (53, "Undefined:53 (LSB)");
MESSAGE (54, "Undefined:54 (LSB)");
MESSAGE (55, "Undefined:55 (LSB)");
MESSAGE (56, "Undefined:56 (LSB)");
MESSAGE (57, "Undefined:57 (LSB)");
MESSAGE (58, "Undefined:58 (LSB)");
MESSAGE (59, "Undefined:59 (LSB)");
MESSAGE (60, "Undefined:60 (LSB)");
MESSAGE (61, "Undefined:61 (LSB)");
MESSAGE (62, "Undefined:62 (LSB)");
MESSAGE (63, "Undefined:63 (LSB)");
MESSAGE (64, "Damper Pedal on/off (Sustain): < 64 is off, otherwise on");
MESSAGE (65, "Portamento On/Off: < 64 is off, otherwise on");
MESSAGE (66, "Sostenuto On/Off: < 64 is off, otherwise on");
MESSAGE (67, "Soft Pedal On/Off: < 64 is off, otherwise on");
MESSAGE (68, "Legato Footswitch: < 64 is Normal, otherwise Legato");
MESSAGE (69, "Hold 2: < 64 is off, otherwise on");
MESSAGE (70, "Sound Controller 1 (default: Sound Variation) (LSB)");
MESSAGE (71, "Sound Controller 2 (default: Timbre/Harmonic Intens.) (LSB)");
MESSAGE (72, "Sound Controller 3 (default: Release Time) (LSB)");
MESSAGE (73, "Sound Controller 4 (default: Attack Time) (LSB)");
MESSAGE (74, "Sound Controller 5 (default: Brightness) (LSB)");
MESSAGE (75, "Sound Controller 6 (default: Decay Time - see MMA RP-021) (LSB)");
MESSAGE (76, "Sound Controller 7 (default: Vibrato Rate - see MMA RP-021) (LSB)");
MESSAGE (77, "Sound Controller 8 (default: Vibrato Depth - see MMA RP-021) (LSB)");
MESSAGE (78, "Sound Controller 9 (default: Vibrato Delay - see MMA RP-021) (LSB)");
MESSAGE (79, "Sound Controller 10 (default undefined - see MMA RP-021) (LSB)");
MESSAGE (80, "General Purpose Controller 5 (LSB)");
MESSAGE (81, "General Purpose Controller 6 (LSB)");
MESSAGE (82, "General Purpose Controller 7 (LSB)");
MESSAGE (83, "General Purpose Controller 8 (LSB)");
MESSAGE (84, "Portamento Control (LSB)");
MESSAGE (85, "Undefined:85");
MESSAGE (86, "Undefined:86");
MESSAGE (87, "Undefined:87");
MESSAGE (88, "High Resolution Velocity Prefix (LSB)");
MESSAGE (89, "Undefined:89");
MESSAGE (90, "Undefined:90");
MESSAGE (91, "Effects 1 Depth");
MESSAGE (92, "Effects 2 Depth (formerly Tremolo Depth)");
MESSAGE (93, "Effects 3 Depth");
MESSAGE (94, "Effects 4 Depth (formerly Celeste [Detune] Depth)");
MESSAGE (95, "Effects 5 Depth (formerly Phaser Depth)");
MESSAGE (96, "Data Increment (Data Entry +1) (see MMA RP-018)");
MESSAGE (97, "Data Decrement (Data Entry -1) (see MMA RP-018)");
MESSAGE (98, "Non-Registered Parameter Number (NRPN) - (LSB)");
MESSAGE (99, "Non-Registered Parameter Number (NRPN) - (MSB)");
MESSAGE (100, "Registered Parameter Number (RPN) - (LSB)");
MESSAGE (101, "Registered Parameter Number (RPN) - (MSB)");
MESSAGE (102, "Undefined:102");
MESSAGE (103, "Undefined:103");
MESSAGE (104, "Undefined:104");
MESSAGE (105, "Undefined:105");
MESSAGE (106, "Undefined:106");
MESSAGE (107, "Undefined:107");
MESSAGE (108, "Undefined:108");
MESSAGE (109, "Undefined:109");
MESSAGE (110, "Undefined:110");
MESSAGE (111, "Undefined:111");
MESSAGE (112, "Undefined:112");
MESSAGE (113, "Undefined:113");
MESSAGE (114, "Undefined:114");
MESSAGE (115, "Undefined:115");
MESSAGE (116, "Undefined:116");
MESSAGE (117, "Undefined:117");
MESSAGE (118, "Undefined:118");
MESSAGE (119, "Undefined:119");
MESSAGE (120, "All Sound Off");
MESSAGE (121, "Reset All Controllers");
MESSAGE (122, "Local Control On/Off: 0 off, 127 on");
MESSAGE (123, "All Notes Off");
MESSAGE (124, "Omni Mode Off (+ all notes off)");
MESSAGE (125, "Omni Mode On (+ all notes off)");
MESSAGE (126, "Mono Mode On (+ poly off, + all notes off)");
MESSAGE (127, "Poly Mode On (+ mono off, +all notes off)");

const char * const messagesTable [128] PROGMEM =
{   
control_0, control_1, control_2, control_3, control_4, 
control_5, control_6, control_7, control_8, control_9, 
control_10, control_11, control_12, control_13, control_14, 
control_15, control_16, control_17, control_18, control_19, 
control_20, control_21, control_22, control_23, control_24, 
control_25, control_26, control_27, control_28, control_29, 
control_30, control_31, control_32, control_33, control_34, 
control_35, control_36, control_37, control_38, control_39, 
control_40, control_41, control_42, control_43, control_44, 
control_45, control_46, control_47, control_48, control_49, 
control_50, control_51, control_52, control_53, control_54, 
control_55, control_56, control_57, control_58, control_59, 
control_60, control_61, control_62, control_63, control_64, 
control_65, control_66, control_67, control_68, control_69, 
control_70, control_71, control_72, control_73, control_74, 
control_75, control_76, control_77, control_78, control_79, 
control_80, control_81, control_82, control_83, control_84, 
control_85, control_86, control_87, control_88, control_89, 
control_90, control_91, control_92, control_93, control_94, 
control_95, control_96, control_97, control_98, control_99, 
control_100, control_101, control_102, control_103, control_104, 
control_105, control_106, control_107, control_108, control_109, 
control_110, control_111, control_112, control_113, control_114, 
control_115, control_116, control_117, control_118, control_119, 
control_120, control_121, control_122, control_123, control_124, 
control_125, control_126, control_127
};  // end of messagesTable
  
const int noRunningStatus = -1;

int runningStatus;
unsigned long lastRead;
byte lastCommand;

void setup() {
  //  Set MIDI baud rate:
  midi.begin(31250);
  Serial.begin(115200);
  Serial.println ();
  Serial.println (F("MIDI Decoder. Author: Nick Gammon."));
  runningStatus = noRunningStatus;
} // end of setup

void RealTimeMessage (const byte msg)
  {
    
  } // end of RealTimeMessage
  
// get next byte from serial (blocks)
int getNext ()
  {

  if (runningStatus != noRunningStatus)
    {
    int c = runningStatus;
    // finished with look ahead
    runningStatus = noRunningStatus;
    return c;
    }
    
  while (true)
    {
    while (midi.available () == 0)
      {}
    byte c = midi.read ();
    if (c >= 0xF8)  // RealTime messages
      RealTimeMessage (c);
    else
      return c;
    }
  } // end of getNext

const char * notes [] = { "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " };

// interpret a note in terms of note name and octave
const char * getNote ()
  {
  byte note = getNext ();
  byte octave = note / 12;
  Serial << notes [note % 12] << F("(") << _DEC (octave - 1) << F(")");
  return " "; 
  }  // end of getNote
  
// dump a system exclusive message in hex
void showSystemExclusive ()
  {
  int count = 0;
  while (true)
    {
    while (midi.available () == 0)
      {}
    byte c = midi.read ();
    if (c >= 0x80)
      {
      Serial << endl;
      runningStatus = c;
      return;  
      }
    
    Serial << _HEX (c) << " ";
    if (++count > 32)
      {
      Serial << endl;
      count = 0;  
      }
    } // end of reading until all system exclusive done
  }  // end of showSystemExclusive
  
/* Print a string from Program Memory directly to save RAM */
void printProgStr (const char * str)
{
  char c;
  if(!str) return;
  while((c = pgm_read_byte(str++)))
    Serial.print(c);
} // end of printProgStr

// show a control message by looking up in the table
void showControlMessage ()
  {
   byte message =  getNext () & 0x7F;
   byte param = getNext ();
   // get address from progmem, display each byte
   printProgStr ((const char *) pgm_read_word(&(messagesTable[message])));
   // show parameter in hex
   Serial << ": 0x" << _HEX (param) << endl;
  }  // end of showControlMessage
  
void loop() 
{
  byte c = getNext ();

  if (((c & 0x80) == 0) && (lastCommand & 0x80))
    {
    runningStatus = c;
    c = lastCommand; 
    }
    
  // channel is in low order bits
  int channel = (c & 0x0F) + 1;

  // show time since last message in microseconds
  char buf [15];
  ltoa (micros () - lastRead, buf, 10);
  byte spaces = 10 - strlen (buf);
  for (byte i = 0; i < spaces; i++)
    Serial << " ";
  
  // show elapsed time
  Serial << buf << ": ";  
  lastRead = micros ();
    
  // messages start with high-order bit set
  if (c & 0x80)
    {
    lastCommand = c;
    switch ((c >> 4) & 0x07)
      {
      case 0: 
        Serial << F("Note off channel = ") << channel << F(", note = ") << getNote () << F(", velocity = ") << getNext () << endl;
        break;
        
      case 1: 
        Serial << F("Note  on channel = ") << channel << F(", note = ") << getNote () << F(", velocity = ") << getNext () << endl;
        break;
        
      case 2: 
        Serial << F("Polyphonic pressure channel = ") << channel << F(", note = ") << getNote () << F(", pressure = ") << getNext () << endl;
        break;
        
      case 3: 
        Serial << F("Control change channel = ") << channel << ", ";
        showControlMessage ();
        break;
        
      case 4: 
        Serial << F("Program change channel = ") << channel << F(", program = ") << getNext () << endl;
        break;
        
      case 5: 
        Serial << F("After-touch pressure channel = ") << channel << F(", value = ") << getNext () << endl;
        break;
        
      case 6: 
        Serial << F("Pitch wheel change channel = ") << channel << F(", value = ") << (getNext () | getNext () << 7) << endl;
        break;
        
      case 7:  // system message
        {
        lastCommand = 0;           // these won't repeat I don't think
        Serial << "System: ";
        switch (c & 0x0F)
          {
          case 0:
            Serial << F("Exclusive (in hex)") << F(", vendor ID = ") << getNext () << endl;
            showSystemExclusive ();
            break;
            
          case 1:
            Serial << F("Time code: value/type = ") << getNext () << endl;
            break;
          
          case 2: 
            Serial << F("Song position pointer, value = ") << (getNext () | getNext () << 7) << endl;
            break;
          
          case 3:
            Serial << F("Song select, song = ") << getNext () << endl;
            break;

          case 4:
            Serial << F("Reserved (4)") << endl;
            break;

          case 5:
            Serial << F("Reserved (5)") << endl;
            break;

          case 6:
            Serial << F("Tune request") << endl;
            break;

          case 7:
            Serial << F("End of exclusive") << endl;
            break;
            
          case 8:
            Serial << F("Timing clock") << endl;
            break;
            
          case 9:
            Serial << F("Reserved (9)") << endl;
            break;

          case 10:
            Serial << F("Start") << endl;
            break;
            
          case 11:
            Serial << F("Continue") << endl;
            break;
            
          case 12:
            Serial << F("Stop") << endl;
            break;
            
          case 13:
            Serial << F("Reserved (13)") << endl;
            break;
            
          case 14:
            Serial << F("Active sensing") << endl;
            break;
            
          case 15:
            Serial << F("Reset") << endl;
            break;
            
          }  // end of switch on system message  
          
        }  // end system message
        break;
      }  // end of switch
    }  // end of if
  else
    Serial << _HEX(c) << endl;
   
}  // end of loop


The code uses the Streaming library to simplify serial output.

Example output



   1191616: Control change channel = 1, General Purpose Controller 1 (MSB): 0x40
      5532: Note off channel = 1, note = D (5) , velocity = 99
      2740: Note  on channel = 1, note = E (0) , velocity = 127
      4528: Note  on channel = 1, note = D (4) , velocity = 63
     46184: After-touch pressure channel = 1, value = 31
     15328: After-touch pressure channel = 1, value = 127
     29776: Note off channel = 1, note = D (5) , velocity = 64
      5708: After-touch pressure channel = 1, value = 49
     10664: After-touch pressure channel = 1, value = 18
     13256: After-touch pressure channel = 1, value = 9


Change instruments:


   1115248: Control change channel = 1, Bank Select (MSB): 0x3F
      1964: Control change channel = 1, Bank Select (LSB): 0x4
      2064: Program change channel = 1, program = 42
      3832: System: Exclusive (in hex), vendor ID = 66
30 7A 40 0 0 44 61 6E 63 65 20 4C 0 65 61 64 20 20 20 20 20 20 20 A 3C 0 30 14 50 27 0 14 5F 
48 2 14 2 28 19 55 0 31 40 2 20 0 40 0 7F 7F 4E 23 0 40 


Another tune:


    486436: Note off channel = 1, note = C (4) , velocity = 64
      2036: Note  on channel = 1, note = G (4) , velocity = 105
      2120: Note off channel = 1, note = D (4) , velocity = 114
      4532: Note  on channel = 1, note = A#(3) , velocity = 105
     75416: Note off channel = 1, note = A (3) , velocity = 64
      6988: Note off channel = 1, note = F (4) , velocity = 64
    503724: Note  on channel = 1, note = A (4) , velocity = 102
      2120: Note  on channel = 1, note = E (4) , velocity = 117
      2212: Note  on channel = 1, note = C (4) , velocity = 99
      4444: Note  on channel = 1, note = B (3) , velocity = 99
     10344: Note off channel = 1, note = B (3) , velocity = 0
     76212: Note off channel = 1, note = D (4) , velocity = 64
      2036: Note off channel = 1, note = A#(3) , velocity = 64
     41684: Note off channel = 1, note = G (4) , velocity = 64
    872724: Note off channel = 1, note = C (4) , velocity = 64
    147928: Note off channel = 1, note = A (4) , velocity = 64


Pitch wheel changes:


 136802728: Note  on channel = 1, note = D#(4) , velocity = 111
     24208: After-touch pressure channel = 1, value = 0
    106028: Pitch wheel change channel = 1, value = 9560
     13056: Pitch wheel change channel = 1, value = 16383
    133788: Note off channel = 1, note = D#(4) , velocity = 64
     19176: Pitch wheel change channel = 1, value = 16045
     11352: Pitch wheel change channel = 1, value = 12053
     11644: Pitch wheel change channel = 1, value = 8192
     72888: After-touch pressure channel = 1, value = 0
     94608: Note  on channel = 1, note = G#(4) , velocity = 105
    217200: Note off channel = 1, note = G#(4) , velocity = 64
      8080: Note  on channel = 1, note = A#(4) , velocity = 105
     38220: After-touch pressure channel = 1, value = 6
     11336: After-touch pressure channel = 1, value = 127
     35416: After-touch pressure channel = 1, value = 42
     11356: After-touch pressure channel = 1, value = 0
     34140: Note off channel = 1, note = A#(4) , velocity = 64
    264208: Note  on channel = 1, note = C#(5) , velocity = 102
    244728: After-touch pressure channel = 1, value = 5
     13324: After-touch pressure channel = 1, value = 21
     11352: After-touch pressure channel = 1, value = 42
     15388: After-touch pressure channel = 1, value = 0
     11360: After-touch pressure channel = 1, value = 80
     11624: After-touch pressure channel = 1, value = 101
     11080: After-touch pressure channel = 1, value = 127
    404408: After-touch pressure channel = 1, value = 42
     11060: After-touch pressure channel = 1, value = 3
     15400: After-touch pressure channel = 1, value = 0
    144136: Note off channel = 1, note = C#(5) , velocity = 64
      4180: Note  on channel = 1, note = A#(4) , velocity = 99
     52116: After-touch pressure channel = 1, value = 15
      9592: After-touch pressure channel = 1, value = 21
     13120: After-touch pressure channel = 1, value = 2
     11332: After-touch pressure channel = 1, value = 0
     64000: Note off channel = 1, note = A#(4) , velocity = 64
    204316: Note  on channel = 1, note = G#(4) , velocity = 96


Demonstration


The above code demonstrated: https://vimeo.com/80328016

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


4,531 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at FutureQuest]