Archive for the ‘General’ Category

Sending MIDI data with the Launchpad Pro

May 10, 2017

Now that we have basic pad handling in place it is time to start doing something musical with the Launchpad Pro.   So I’m going to replicate something akin to the chromatic keys mode of the Launchpad Pro?  No, I’m not going to replicate the whole new scales mode that was released with firmware version 1.4, but we’ll step generally into that direction.

There are two key parts to this:

  • Initial rendering of the display
  • Reacting to the key press and generating the midi event

Rendering the Chromatic Key Display

I’m going to make a few simplifying assumptions for the purpose of this demonstration:

  1. The tonic will be C and nothing else, no changes
  2. No shifting of the octaves – what’s on grid is what you get

So, given those assumptions we can start with pad 11 as C2, which is MIDI note 36 (check out the nice reference at midikits.net and we can initialize our grid data from there using Novation’s partially overlapping row layout.

#define TONIC_R  (32)
#define TONIC_G  (0)
#define TONIC_B  (32)

#define WHITEKEY_R (0)
#define WHITEKEY_G (0)
#define WHITEKEY_B (63)

#define BLACKKEY_R (0)
#define BLACKKEY_G (0)
#define BLACKKEY_B (0)

#define BLACK_R (0)
#define BLACK_G (0)
#define BLACK_B (0)

u8 notegrid[100];
u8 colorgrid[100];
u8 tonic = 36;

void app_init()
{
  u8 note = tonic ;

  for ( u8 i = 0 ; i < 100 ; i++ ) 
  {
    if ( i < 10  || i > 90) 
    {
      // lowest or highest row
      notegrid[i] = -1 ;
      colorgrid[i] = -1 ;
      hal_plot_led( TYPEPAD, i, BLACK_R, BLACK_G, BLACK_B ) ;
    }
    else if ( i % 10 == 0 )
    {
      // left column
      if ( i > 10 )
      {
        note -= 3; // new row so back up the note index a bit
      }
      
      notegrid[i] = -1 ;
      colorgrid[i] = -1 ;
      hal_plot_led( TYPEPAD, i, BLACK_R, BLACK_G, BLACK_B ) ;
    }
    else if ( i % 10 == 9 )
    {
      // right column
      notegrid[i] = -1 ;
      colorgrid[i] = -1 ;
      hal_plot_led( TYPEPAD, i, BLACK_R, BLACK_G, BLACK_B ) ;
    }
    else
    {
      notegrid[i] = note;
      if ( IsNoteTonic(note) )
      {
        colorgrid[i] = 2 ;  //flag for tonic
        hal_plot_led( TYPEPAD, i, TONIC_R, TONIC_G, TONIC_B ) ;
      }
      else if ( IsNoteWhiteKey(note) )
      {
        colorgrid[i]  =1 ; // flag for white key
        hal_plot_led( TYPEPAD, i, WHITEKEY_R, WHITEKEY_G, WHITEKEY_B ) ;
      }
      else
      {
        colorgrid[i] = 0 ; // flag for black key
        hal_plot_led( TYPEPAD, i, BLACKKEY_R, BLACKKEY_G, BLACKKEY_B ) ;
      }

      note++ ;
    }
  }
}

This code will initialize arrays of note and color data as well as set the initial pad colors.  The colors should looks something similar to the normal Novation chromatic scale mode.

Determining the colors of the musical pads comes down to a couple of functions that rely on midi data arrangements:

u8 IsNoteTonic( u8 note )
{
  // any multiple of tonic + 12 will mod to 0
  return (( (note-tonic) % 12) == 0) ;  
}

u8 IsNoteWhiteKey( u8 note )
{
  if ( (note %12) == 0 ) return 1; //C
  if ( ((note-2)%12) == 0) return 1; //D
  if ( ((note-4)%12) == 0) return 1; //E
  if ( ((note-5)%12) == 0) return 1; //F
  if ( ((note-7)%12) == 0) return 1; //G
  if ( ((note-9)%12) == 0) return 1; //A
  if ( ((note-11)%12) == 0) return 1; //B
  return 0 ;
}

u8 IsNoteBlackKey(u8 note)
{
  if ( ((note-1) %12) == 0) return 1; //C#
  if ( ((note-3) %12) == 0) return 1; //D#
  if ( ((note-6) %12) == 0) return 1; //F#
  if ( ((note-8) %12) == 0) return 1; //G#
  if ( ((note-10) %12) == 0) return 1; //A#
  return 0;
}

N.B. we could flip the white/black key assignment logic in app_init by using the IsNoteBlackKey function and save a couple of CPU cycles since the IsNoteBlackKey function is two if-statements shorter in the worst case.

Sending MIDI

The hal_send_midi function is what we are really interested in today.  It takes 4 parameters, the first of which is the port to send the MIDI data out on.  We can choose DINMIDI for the traditional 5 pin DIN MIDI connector, or USBMIDI to send out the USB connection (to be received by something like Ableton).  There is also USBSTANDALONE, but unfortunately I have been unable to locate any documentation about that setting.

The last three parameters are the heart of MIDI data.  Many MIDI messages amount to sending three bytes of data: a MIDISTATUS byte and two data bytes.  For this post I’m only interested in sending NOTE ON and OFF messages.  The MIDI status byte is divided into two nibbles where the high nibble indicates the type of message (NOTE ON, NOTE OFF, PROGRAM CHANGE, CONTROL CHANGE, etc.) and the low nibble indicates the MIDI channel.   If we set up constants for NOTE ON and OFF with the data in the high nibble (i.e. 0x90 for on and 0x80 for off), we can bit-wise OR the channel number into the low lower nibble.  For example:

u8 midistatus = 0x90 | 0x01 ;  // MIDI NOTE ON for channel 2

The remaining two data bytes are specific to each type of message.  In our case, NOTE ON/OFF requires the note to play (we can use the notegrid array for that value) and the velocity of the pad press.

The app_surface_event method is interesting in that while it provides a velocity value (the third parameter “value”), it really only allows for key press velocity, not key off velocity.  It uses a value of 0 to indicate key off and 1-127 as the key on velocity.  So, in our case we can use the value parameter to tell us to send a MIDI NOTE ON or OFF, and use it to send the MIDI NOTE ON velocity value.  For NOTE OFF we will have to make something up…

Putting it all together

Let’s put that to use in the app_surface_event function:

#define MIDI_STATUS_NOTE_OFF (0x80)
#define MIDI_STATUS_NOTE_ON (0x90)

// actual midi channel is 6
// as that's where my http://www.audiothingies.com/product/micromonsta/ is...
u8 midiChannel = 5; 

void app_surface_event(u8 type, u8 index, u8 value)
{
  if ( type == TYPEPAD )
  {
    if ( index < 10 || index > 90 )
    {
      // top or bottom rows
    }
    else if ( (index % 10 == 0) || (index % 10 == 9) )
    {
      // left or right columns
    }
    else
    {
      // ONLY CARE ABOUT THE CENTER 64 PADS
      u8 lightUpLowerRow = (index % 10 < 4) && (index > 20);
      u8 lightUpUpperRow = (index % 10 > 5) && (index < 80);

      // VALUE == 0 INDICATES PAD BEING RELEASED
      if ( value == 0 )
      {
        if ( colorgrid[index] == 2 )
        {
          hal_plot_led( TYPEPAD, index, TONIC_R, TONIC_G, TONIC_B ) ;

          if ( lightUpLowerRow )
          {
            // light up the pad on the lower row
            hal_plot_led( TYPEPAD, index-5, TONIC_R, TONIC_G, TONIC_B ) ;
          }
          else if ( lightUpUpperRow )
          {
            // light up the pad on the upper row
            hal_plot_led( TYPEPAD, index+5, TONIC_R, TONIC_G, TONIC_B ) ;
          }
        }
        else if ( colorgrid[index] == 1 )
        {
          hal_plot_led( TYPEPAD, index, WHITEKEY_R, WHITEKEY_G, WHITEKEY_B ) ;

          if ( lightUpLowerRow )
          {
            hal_plot_led( TYPEPAD, index-5, WHITEKEY_R, WHITEKEY_G, WHITEKEY_B ) ;
          }
          else if ( lightUpUpperRow )
          {
            hal_plot_led( TYPEPAD, index+5, WHITEKEY_R, WHITEKEY_G, WHITEKEY_B ) ;
          }
        }
        else
        {
          hal_plot_led( TYPEPAD, index, BLACKKEY_R, BLACKKEY_G, BLACKKEY_B ) ;

          if ( lightUpLowerRow )
          {
            hal_plot_led( TYPEPAD, index-5, BLACKKEY_R, BLACKKEY_G, BLACKKEY_B ) ;
          }
          else if ( lightUpUpperRow )
          {
            hal_plot_led( TYPEPAD, index+5, BLACKKEY_R, BLACKKEY_G, BLACKKEY_B ) ;
          }
        }
        // SEND MIDI NOTE OFF with high velocity since we don't know what it was
        hal_send_midi( DINMIDI, MIDI_STATUS_NOTE_OFF | midiChannel, notegrid[index], 127) ;
      }
      else
      {
        hal_plot_led( TYPEPAD, index, KEYPRESS_R, KEYPRESS_G, KEYPRESS_B ) ;

        if ( lightUpLowerRow )
        {
          // light up the pad on the lower row
          hal_plot_led( TYPEPAD, index-5, KEYPRESS_R, KEYPRESS_G, KEYPRESS_B ) ;
        }
        else if ( lightUpUpperRow )
        {
          // light up the pad on the upper row
          hal_plot_led( TYPEPAD, index+5, KEYPRESS_R, KEYPRESS_G, KEYPRESS_B ) ;
        }
        
        //SEND MIDI NOTE ON with provided value as velocity
        hal_send_midi( DINMIDI, MIDI_STATUS_NOTE_ON | midiChannel, notegrid[index], value) ;
      }
    }
  }
}

I don’t love all that duplication of if-statements but there’s really only one real alternative algorithm and it leads to less readable code for no significant benefit.

Wrapping up

Throw that all in an app.c, compile it (with the missing callbacks) and load it into your Launchpad Pro.  You will end up with an overlapping chromatic grid similar to the normal one that emits to MIDI channel 6.

Steps beyond this would be:

  • Support moving through the octaves
  • Hook the SETUP button to allow the user to:
    • pick a MIDI channel
    • pick a scale
    • Pick a key

Starting this off

January 2, 2010

A bit over a year ago I started the Web Ministry Thoughts blog.  Despite the tapering off of the writing I enjoy writing for the blog and plan to continue it.  However, I have found its purpose to be a bit narrow for the types of things I found that I would like to write about: coding, software development, and walking the Christian walk.

Recently, I had to replace the engine in my car and was talking to an ex-student/ex-employee/friend-of-mine about where I was getting my car worked on.  He reviewed the web site and commented to the effect:  “oh no, a car mechanic for Jesus”.  Knowing my friend it was received as the good-natured dig it was intended – he knows well my Christian background.  As I considered what I wanted to call this blog I considered his blog’s name (Martial Coder) and decided I liked the idea of being called a coder (as that’s what I am) and that as such and as a Christian I am a coder coding for Christ.  A bit trite I suppose, but it will accomplish my purposes which hopefully are also Christ’s purposes for my life.