Improv How to: Basic MIDI output

    The most convenient way to get MIDI output is to use the MidiOutput class. Example 1 shows a simple program which sends out a note-on MIDI message to a synthesizer connected to port 0.

      Example 1: simple MIDI output program.
       1
       2
       3
       4
       5
       6
       7
       8
      
      #include "improv.h"
      
      int main(void) {
         MidiOutput midi(0);
         midi.play(0, 60, 127);
      
         return 0;
      }
      

    Code explanations for Example 1:

  • #include "improv.h"
    This line of code includes the improv.h header file, which in turn, includes all possible definitions needed for using the improv library. This example program uses only one class, so you could have instead included the file MidiOutput.h, since only that class was used from the improv library.

  • MidiOutput midi(0);
    This line creates a variable called midi which is of type MidiOutput. The midi variable is set to send MIDI out on port 0, which is the default port for MIDI output. However, if you do not specify the output port when creating a MidiOutput object, you will need to specifically open the MIDI connection, like this:
          MidiOutput midi;
          midi.open();
       
    which would open MIDI output port 0, or to open a specific port:
          MidiOutput midi;
          midi.setPort(1);
          midi.open();
       
    which would open MIDI output port 1.

  • midi.play(0, 60, 127);
    This line sends a note-on message to port 0. The three parameters of the play function are:
    1. channel -- the MIDI channel number to play the note on. A channel is in the range from 0 to 15. Note that MIDI channel 1 is 0 for this function, and MIDI channel 15 is 15.
    2. note -- the MIDI key number to play, which is an integer in the range from 0 to 127. Middle C being note 60, C# being 61, D being 62, etc.
    3. volume -- the attack velocity which is a number between 1 and 127. 1 is the softest and 127 is the loudest possible. A special case is an attack velocity of 0 which means to turn a previously struck note off.


Selecting a MIDI output port

    The MidiOutput class contains a list of all of the MIDI output ports which are available for your program. Example 2 shows how to send a MIDI message to all MIDI output ports:

      Example 2: sending MIDI message to all output ports.
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      #include "improv.h"
      
      int main(void) {
         MidiOutput midi;
         int portCount = midi.getNumPorts();
         for (int i=0; i<portCount; i++) {
            midi.setPort(i);
            midi.open();
            midi.play(0, 60, 127);
            cout << "Played a note on: " 
                 << midi.getName() << endl;
         }
      
         return 0;
      }
      

    Note that the midi port had to be opened the first time a particular port was used.

    Code explanations for Example 2:

  • midi.getNumPorts()
    This line returns the total number of MIDI output ports available to the program. All ports are indexed starting from 0 to one less than the total count. The midi variable can change ports using these valid numbers by calling the setPort() function.

  • midi.getName()
    This line of code returns a string provided by the MIDI driver as a description of the MIDI output port. It usually tells you what type of internal sound synthesizer or external MIDI connection the port sends its data to.

  • Example 3 shows a useful piece of code that displays all of the possible MIDI out port names. This code could be used in a program that allows the user to select a particular MIDI output port to send on.

      Example 3: printing a menu of MIDI output ports.
      #include "improv.h"
      
      int main(void) {
         MidiOutput midi;
         int selection = -1;
         int portCount = midi.getNumPorts();
      
         for (int i=0; i<portCount; i++) {
            midi.setPort(i);
            cout << i << ":" << midi.getName() << endl;
         }
         cout << "Select an output port: ";
         cin  >> selection;
      
         // play a note on the selected port:
         midi.setPort(selection);
         midi.open();
         midi.play(0, 60, 127);
      
         return 0;
      }
      

Types of MIDI output

    There are several C++ classes related to MIDI output in the improv library, including the following classes:

  • MidiOutPort
  • The lowest-level improv class for dealing with MIDI output. This class defines the basic commands for sending MIDI messages to the MIDI ports. All other MIDI output classes are derived from this class and therefore have the same functionality as this class. Use this class only if you don't need any useful helper functions for MIDI output.
  • MidiPort
  • This class is not too useful, but exists for completeness. It has the functionality of both the MidiInPort and MidiOutPort classes.
  • MidiOutput
  • The most useful general-purpose class for MIDI output. It defined many convenience functions for using the various MIDI messages. For example the function MidiOutput::play(0, 60, 127) is equivalent to the function MidiOutPort::rawsend(0x90, 60 127).
  • Voice
  • Voice is a specialized type of MidiOutput. The purpose of the Voice class is to keep track of note-off commands. When ever a new note is played throgh a Voice object, the previous note will automatically be turned off if necessary.
  • MidiPerform
  • A specialized form of MidiOutput which controls the performance of the standard MIDI file.
  • MidiIO
  • Combines the functions of MidiOutput and MidiInput.
  • Synthesizer
  • A specialized form of MidiIO which sorts MIDI input data into buffers of notes and controllers.
  • RadioBaton
  • A specialized form of MidiIO which parses MIDI input data for Radio Baton messages as well as defined special MIDI messages for communication with a Radio Baton controller.

Monophonic looping

    Example 4 shows a random-walk algorithm using the Voice class for MIDI output. This program outputs a monophonic line of random notes that are centered around Middle C (note 60). If the notes wander too far from Middle C, then the notes will be transposed to the other side of Middle C.

      Example 4: Random walk in the octave.
      #include "improv.h"
      
      int main(void) {
         Voice midi(0);
         int note = 6;
         int step = 0;
      
         while (1) {
            step = rand() % 2;
            step ? note++ : note--;
            if (note < 0) note += 12;
            midi.play(54 + note % 12, 64);
            millisleep(250);
         }
      
         return 0;
      }
      

    Code explanations for Example 4:

  • Voice midi(0);
    The Voice class is a type of MidiOutput which keeps track of note-off messages automatically so that you do not have to send explicit note off messages. The midi variable is set here to use port 0 for MIDI output.

  • step = rand() % 2;
    the step variable is assigned either the value 0 or 1 in a random manner.

  • step ? note++ : note--;
    if step is equal to 1 then the note number will increase by one, if step is 0, then the note will decrease by one.

  • if (note < 0) note += 12;
    the C language behaves in a funny manner when using the mod operator (%) on negative numbers, so this line of code makes sure that note is positive.

  • midi.play(54 + note % 12, 64);
    Play a note in the range between MIDI keynumber 54 and 67 inclusive at medium loudness. Note that the MIDI channel number is not specified -- the Voice class will automatically use the last channel for the current note (defaulting to MIDI channel 1).

  • millisleep(250);
    Pause the program for 250 milliseconds. This will give a tempo of quarter-note at 60 Beat per Minute if you consider the individual notes as being sixteenth-notes. The millisleep() function is defined with the SigTimer class and is useful for porting the program between operating systems (i.e., Windows 95 and Linux millisecond sleeping functions have different names).

Barebones MIDI sequencer

    The MidiPerform class can be used to play standard MIDI files. Example 5 shows a program for a simple MIDI sequencer.

      Example 5: Simple MIDI sequencer.
      #include "improv.h"
      
      int main(int argc, char** argv) {
         if (argc != 2) {
            cout << "Usage: " << argv[0] 
                 << " midifile" << endl;
            exit(1);
         }
      
         MidiPerform performance;
         performance.setPort(0);
         performance.open();
         performance.read(argv[1]);
         performance.setTempo(120);
         performance.setTempoMethod(TEMPO_METHOD_AUTOMATIC); 
      
         while (1) {
            performance.check();
            millisleep(1);
         }
      
         return 0;
      }
      

    Code explanations for Example 5:

  • performance.read(argv[1]);
    Read in a standard MIDI file.

  • performance.setTempo(120);
    Set the tempo of the performance to 120 beats per minute.

  • performance.setTempoMethod(TEMPO_METHOD_AUTOMATIC);
    Use automatic tempo control. You can also control the tempo with a computer keyboard, or MIDI keyboard, etc.

  • performance.check();
    Continuously check the performance for MIDI notes to play. if you are using automatic tempo control, you know beforehand when you will need to play the next note, so you could calculate a specific length of time to sleep.

  • There is an example program, midiperform.cpp, which is a more complex version of this programming example which adds many user controls from the computer keyboard and synthesizer keyboard.






This How to was last updated on Wed Dec 1 12:47:12 PST 1999
by craig@ccrma.stanford.edu