O
[Sect1] [Sect2] [Sect3] [Sect4] [Sect5] [Sect6] [Sect7] [Sect8] [Sect9]

Scales using MIDI note numbers


Sect.1

MIDI - not an overview

MIDI, Musical Instument Digital Interface, is a computer protocol that allows electronic musical instrument manufacturers to design devices that are compatible across all brand names. MIDI is not terribly complicated yet a full explanation is beyond the scope of this lecture. I'll define the elements that are relevant to this discussion as they come up.

MIDI comunicates by sending messages (a package of bits grouped together to form some predefined code) along the MIDI cable that connects two MIDI devices. These MIDI cables are plugged in by the user (you or me) into the MIDI jacks (either IN, OUT or THRU, THRU sends the IN messages back out so you can chain several MIDI devices together) It is a one way flow of information in the MIDI cable. As an example, consider a synthesizer and a drum machine used together. You could conect the synthesizer's MIDI OUT to the drum machine's MIDI IN (using a MIDI cable). Now the magic happens, when you play the synthesizer, the drum machine will sound different percussion sounds according to which key on the synthesizer you are pressing. If you want the button on the drum machine to activate the synthesizer, then you have to connect another MIDI cable from the drum machine's MIDI OUT to the synthesizer's MIDI IN. In it's standard protocol, MIDI travels down one way streets.

Some current techonologies have allowed for different type of hook ups, but within the standard protocol of MIDI you normally hook the MIDI OUT (or MIDI THRU) of device-1 into the MIDI IN of device-2, if you need two way communication then you would connect device-2's MIDI OUT to device-1's MIDI IN.

I remember the first time I hooked up my DX7 to my DrumTrax machine in 1985. I pressed keys on the DX7 and drum sounds started coming out of the drum machine and I broke out laughing. It was a fairly new technology then but now it's as common as earthquakes in California.

Another great thing about MIDI is that it spells "THRU" the way it should be spelled.

Sect.2

MIDI NOTE ON message

For our discussion the important MIDI message is the Note On message. The Note On message contains four pieces of information, (1) Note On code (2) MIDI channel (3) note number (4) velocity value. For our discussion only the note number is relevant. Computer software will of course use all 4. The note number relates to the specific key that was pressed. Middle C is designated as "60" in MIDI talk. Although the concept of "keys" does exist in MIDI sequence files, Note On message don't distinguish between C# and Db, it's just 61 to MIDI (each MIDI number represents one half step, so 61 is one half step above 60). If we start a major scale on middle C, we can write out the values in MIDI note numbers.

MIDI Note Number60 62 64 65  67 69 71 72 
Number of half steps 2 2 1 2 2 2 1
Note Names
Whole / Half W W H W W W H

Hmm, the last note C has a MIDI note number of 72, a difference of 12 from the starting C. That makes sense, there are 12 half steps in an octave. So if middle C is MIDI note number 60, then what is the MIDI note number for the C one octave lower?

I'll wait  . . . . . . .

 

Yes, 48 is the MIDI note number for that C.

Sect.3

Computer terms

There are several ways you could use the MIDI note number to create MIDI NOTE ON messages to send to devices. The actual programming is very complex and a discussion here is not appropriate even if I was qualified to present one. But the logical principles relate directly to the material we have been studying in this course.

Before continuing, I need to define the concept of an "array" and "loops" as they relate to computer programming. In computer programming the are many different things that fall under the general category of "variables". As its name states it holds a value that can vary. At one moment it is set to a certain value, later the program might change it to a different value (according to the programmer's code).

An array is a collection of variables (in our case numbers) that are referenced by a single name along with an index number. For example the set of numbers 15, 2, 33 and 42 could be stored in an array named "numbers" like this:

numbers[1] = 15
numbers[2] = 2
numbers[3] = 33
numbers[4] = 42

(usually arrays start at 0 instead of 1)

I've selected arbitrary numbers in this case, but if these were numbers of importance, you could access all of them using what is known as "loop". Here's a simple example of how the loop works. Using two simple variables (not arrays) named "index" and "my_number", we can create a loop. I'll use a notation known in computer programming circles as "psuedo code". It isn't any real programming language (although PYTHON looks somewhat like psuedo code) but it shows you the basic logic.


index = 1  (set index to equal 1)
begin loop 
  check if index is less than 5, if it is, do the loop, 
     otherwise jump out the loop.
  my_number = numbers[index]
  do something with my_number
  ...
  add 1 to index and go to beginning of the loop
end loop

Here is what happens each time thru the loop. (The line numbers indicate which time thru the loop.)

  1. index equals 1, numbers[1] is 15 so my_number is set to 15
  2. index equals 2, numbers[2] is 2 so my_number is set to 2
  3. index equals 3, numbers[3] is 33 so my_number is set to 33
  4. index equals 4, numbers[4] is 42 so my_number is set to 42
  5. index equals 5, so the loop is finished

The "do something with my_number" section is where the program performs its magic. The loop is used to merely get the different numbers that have been stored in "numbers[]". Once you get the number, you perform the magic (process the number somehow).

Another important term is "function". A function is a chunk of computer code that performs a specific task. It is often used several time in a program so it is efficient to write the code once and then "call" the function name instead of writing out the code each time. The "do something with my_number" section in the above loop example would call one or more functions. Often a function will have "parameters" that the function uses to do a task that needs some addition information. As an example, in a class grading program, you might define a function that enters a. grade into a file. The parameters might be

  1. the student's name
  2. the name of the test
  3. the grade.

The function, in pseudo code, might look like this:

EnterGrade(student_name, test_name, score)

In the above case, the parameters student_name, test_name, and score are "fed" to the function EnterGrade().

A deep understanding isn't neccessary right now, I'm simply introducing the terminology. Here is a list of the computer terms used in the remaining discussion.

So what's this have to do with MIDI and scales? I thought you'd never ask. We're not thru yet.

Sect.4

Using MIDI numbers to create scales

If you wanted to write a program that could play scales, you would definitely use arrays. We've used arrays to learn scales also. Did you notice? Remember this array: W W H W W W H. Yeah, that's the major scale array. Well we didn't call it that at the time but that is one way a computer programmer might view it. More convenient however, is to think in terms of numbers so now our mantra, eh, array is this: 2, 2, 1, 2, 2, 2, 1. We'll use this array to create a scale player in a moment but first let's get the basic loop structure together. Remember the technique for building scales? As a review here's the major scale.

  1. Start on the first note
  2. second note is up a whole step
  3. third note is up a whole step
  4. fourth note is up a half step
  5. fifth note is up a whole step
  6. sixth note is up a whole step
  7. seventh note is up a whole step
  8. eighth note is up a half step

That would make an eight step process without using a loop. It could be done that way. But it's not very clever or flexible.

Assume that PlayMIDI(a_note) represents tons of computer code (called a function) that can send a MIDI NOTE ON message from the computer's MIDI OUT jack. (MIDI devices will connect their MIDI IN to the computer's MIDI OUT) In reality we need the computer to send more than a NOTE ON message because we also need a NOTE OFF message after the appropriate amount of time has elapsed. For the purposes of this discussion I'll omit those details and assume that the note is a constant duration and it gets shut off somehow. We are primarily concerned with determining which note to play, not how long to play it. The "a_note" variable determines which MIDI note number is put into the MIDI NOTE ON message prior to it being sent out. The PlayMIDI() function logic is not shown here, we'll leave that to the real programmers. PlayMajorScale() is the name given to our scale playing function. The variable "starting_note" used in the parenthesis is used to set the starting value of "a_note" The logic of the PlayMajorScale() function is shown below. Here's the basic idea in psuedo code.

NOTE: If you've never studied computer languages you might be perplexed by the seemingly weird algebra "a_note = a_note +2". It isn't algebra, it's a variable being updated to a new value, in this case, we add 2 to variable "a_note".

functions:
PlayMIDI(a_note)
PlayMajorScale(starting_note)

variables:
a_note
starting_note

function ------------------------------------------
PlayMajorScale(starting_note)
  a_note = starting_note 
  PlayMIDI(a_note) - wait for duration

  a_note = a_note + 2  (a_note now equals 62)
  PlayMIDI(a_note)
  
  a_note = a_note + 2  (a_note now equals 64)
  PlayMIDI(a_note)

  a_note = a_note + 1  (a_note now equals 65)
  PlayMIDI(a_note)

  a_note = a_note + 2  (a_note now equals 67)
  PlayMIDI(a_note)

  a_note = a_note + 2  (a_note now equals 69)
  PlayMIDI(a_note)

  a_note = a_note + 2  (a_note now equals 71)
  PlayMIDI(a_note)

  a_note = a_note + 1  (a_note now equals 72)
  PlayMIDI(a_note)
end function PlayMajorScale() -------------------

we could use the function like this:

starting_note = 60
PlayMajorScale(starting_note)

We used all of the values in our major scale array but we didn't use them AS an array and we didn't use a loop to go thru the repetitive process. A better and potentially more flexible version would use a loop. If you're still interested meet me in the next section.

Sect.5

Using Scale arrays

When we create scales we do a repetitive task, mainly, finding the next note. We are looping thru this repetitive task, each time using the next value in our major scale array to help us find the correct note. You might not have thought about in that way before but that pretty much sums up the process. We will store our major scale array, 2, 2, 1, 2, 2, 2, 1 in a variable named, hmmm, let's see, what's a good name?,... OK, let's use "major_scale".

We need a way to tell our function how many numbers are in our scale array. Since arrays start at index "0" not "1" we can use the vacant major_scale[0] location to store the info we need. There are more efficient ways of write this in computer talk, but the basic idea is as follows:

major_scale[0] = 8 (upper limit used in loop)
major_scale[1] = 2
major_scale[2] = 2
major_scale[3] = 1
major_scale[4] = 2
major_scale[5] = 2
major_scale[6] = 2
major_scale[7] = 1

We have an array variable named "major_scale[]" which contain 8 numbers, the first is the total count of numbers and the following numbers represent the intervals between the adjacient notes of the major scale. we can loop thru and do the repetitive task using a new number from the array each time thru the loop. Here's the logic, it's just like we humans use, wow imagine that. This time we'll call our scale player "PlayScale()".

functions:
PlayScale(starting_note, scale_array)
PlayMIDI(a_note)

variables:
starting_note
a_note
index
interval
scale_array
limit

function-------------------------------
PlayScale(starting_note, scale_array)
  limit = scale_array[0]
  index = 1  (set index to equal 1)

  a_note = starting_note
  PlayMIDI(a_note)  
  begin loop 
    check if "index" is less than "limit", if it is, do the loop, 
       otherwise jump out the loop.
    interval = scale_array[index]
    a_note = a_note + interval
    PlayMIDI(a_note)
    add 1 to index and go to beginning of the loop
  end loop
end function PlayScale()-------------


The function could play a C major scale when it is used like this:

starting_note = 60
scale_array = major_scale
PlayScale(starting_note, scale_array)

Here a blow-by-blow account of what happens with these last three lines:

PlayScale() creates the same scale as PlayMajorScale() but it does it with a loop and it uses a different value of the scale array each time thru the loop (in this case the scale array is set to a "major_scale" array).

Even though it may seem like more steps this way, the computer is so fast it is no problem, this is exactly the kind of thing the computer does really well. This is a more flexible way of doing it because we can feed the PlayScale() function a different scale array!

The difficult MIDI programming is hidden inside the function PlayMIDI(a_note) but all of the music logic is shown above in PlayScale().

Sect.6

More Scale arrays

We have learned the arrays for the minor scales. Let's present them in our computer array form:

natural_minor[0] = 8 (upper limit used in function)
natural_minor[1] = 2
natural_minor[2] = 1
natural_minor[3] = 2
natural_minor[4] = 2
natural_minor[5] = 1
natural_minor[6] = 2
natural_minor[7] = 2

Most programming languages allow you to set-up arrays in an easier manner:
natural_minor[] = "8, 2, 1, 2, 2, 1, 2, 2"
(remember the number 8 is used in the function to set the limit of the loop)

using this shorter version, here's Harmonic minor.

harmonic_minor[] = "8, 2, 1, 2, 2, 1, 3, 1"


and finally Melodic minor.

melodic_minor[] = "8, 2, 1, 2, 2, 2, 2, 1"

If we use the PlayScale() function with one of these scale arrays it will play a minor scale.

starting_note = 62 (the note D)
scale_array = harmonic_minor
PlayScale(starting_note, scale_array) (the function will play D Harmonic minor)

starting_note = 53 (the note F)
scale_array = melodic_minor
PlayScale(starting_note, scale_array) (the function will play F Melodic minor)

This function "thinks" like we do, it uses a scale formula to create the scale. Feed it a different formula, you get a different scale. You can feed it all sorts of scales. Here are some other scale arrays, the numbers (except for the first) represent the intervals between the scale degrees. These scale patterns (and more) are in the cerebral computer memory of improvising musicians. Improvising musicians also have a PlayScale() function packed away in their brain.

dorian       = "8,2,1,2,2,2,1,2"
phrygian     = "8,1,2,2,2,1,2,2"
lydian       = "8,2,2,2,1,2,2,1"
mixolydian   = "8,2,2,1,2,2,1,2"
aeolian      = "8,2,1,2,2,1,2,2"
locrian      = "8,1,2,2,1,2,2,2"

lydian_domiant   = "8,2,2,2,1,2,1,2"
super_locrian    = "8,1,2,1,2,2,2,2"

minor_pentatonic = "6,3,2,2,3,2"
major_pentatonic = "6,2,2,3,2,3"
minor_blues      = "7,3,2,1,1,3,2"
major_blues      = "7,2,1,1,3,2,3"

whole_half_diminished  = "9,2,1,2,1,2,1,2,1"
half_whole_diminished  = "9,1,2,1,2,1,2,1,2"

starting_note = 64
scale_array = minor_blues
PlayScale(starting_note, scale_array)  (plays the E minor blues)

  "my baby left me...and my dog did too...whoa, whoa, I got the blues"

Sect.7

Intervals

This idea is easy to extend to intervals. They can be used just like scales but they only have two notes, a starting_note and an interval. The interval is an offset number equal to the number half steps in that interval. For example, the interval of minor 2nd can be represented by the number 1, a major 2nd is 2, a minor 3rd is 3, a major 3rd is 4 and so on.

The appropriate function would look like this:

function
PlayInterval(starting_note, interval)
  a_note = starting_note
  PlayMIDI(a_note)  (wait for duration)

  a_note = starting_note + interval
  PlayMIDI(a_note)  (wait for duration)
end function PlayInterval()

To make it easier to deal with names instead of numbers, programmers often define a bunch of "constants" as shown below. I've include the most common enharmonics (i.e. aug_2nd and mi_3rd both equal 3).

constants
intervals
p_unison  = 0
mi_2nd    = 1
ma_2nd    = 2
aug_2nd   = 3
mi_3rd    = 3
ma_3rd    = 4
p_4th     = 5
aug_4th   = 6
dim_5th   = 6
p_5th     = 7
aug_5th   = 8
mi_6th    = 8
ma_6th    = 9
dim_7th   = 9
aug_6th   = 10
mi_7th    = 10
ma_7th    = 11
p_octave  = 12

note names... incomplete list
C4 = 60
C#4 = 61
Db4 = 61
D4 =  62
D#4 = 63
Eb4 = 63
...

Now you don't have to translate everything into numbers. Instead the functions can be called like this.

PlayInterval(D4, mi_7th)  (it plays D4 followed by C5)

PlayInterval(Eb4, - p_5th)   (it plays Eb4 followed by Ab3, notice the minus sign.
                              this is a descending interval)

Sect.8

Chords

Chords can be represented with arrays just like scales. The difference is that with chords several notes play together instead of one after another. The array for triads need only contain three numbers. The first is again used to set the limit of the loop and the second and third numbers represent the interval from the root-to-third and third-to-fifth respectively.

ma_triad[]   = "3,4,3"
mi_triad[]   = "3,3,4"
dim_triad[]  = "3,3,3"
aug_triad[]  = "3,4,4"

The function PlayChord() is the same as PlayScale() except that the loop doesn't wait for the note to finish, instead it zips thru the loop so quickly that all of the notes will appear to sound at the same time. Since I've been ignoring the details of turning the NOTE ON message off, the function looks the same as PlayScale() but with some different parameter names.

functions:
PlayChord(root, chord_array)
PlayMIDI(a_note)

variables:
root
a_note
index
interval
chord_array
limit

function-------------------------------
PlayChord(root, chord_array)
  limit = chord_array[0]
  index = 1  (set index to equal 1)

  a_note = root
  PlayMIDI(a_note)  
  begin loop 
    check if "index" is less than "limit", if it is, do the loop, 
       otherwise jump out the loop.
    interval = chord_array[index]
    a_note = a_note + interval
    PlayMIDI(a_note)  (DONT WAIT, loop again)
    add 1 to index and go to beginning of the loop
  end loop
end function PlayChord()-------------


The function could play some triads when it is used like this:

PlayChord(C4, major_triad)

PlayChord(Eb4, augmented_triad)

By using negative numbers you can play inversions. The second and third numbers represent the interval from the root-to-third and third-to-fifth respectively. Note that in the 1st inversion arrays the interval from root-to-third is down a sixth instead of up a third (making the third of the chord the lowest tone). Likewise, in the 2nd inversion arrays the interval from third-to-fifth is down a sixth instead of up a third (making the fifth of the chord the lowest tone).

ma_triad_1inv[]   = "3,-8,3"
mi_triad_1inv[]   = "3,-9,4"
dim_triad_1inv[]  = "3,-9,3"
aug_triad_1inv[]  = "3,-8,4"

ma_triad_2inv[]   = "3,4,-9"
mi_triad_2inv[]   = "3,3,-8"
dim_triad_2inv[]  = "3,3,-9"
aug_triad_2inv[]  = "3,4,-8"

PlayChord(C5, mi_triad_1inv)

PlayChord(Bb4, ma_triad_2inv)

Sect.9

Links

If you want ot pursue programming in MIDI, check out MIDIShare a free MIDI API (application programmer's interface) for the Mac and Windows 95/98. It was created by grame research in france. I found that reading the documentation gave me a better idea how MIDI programming works. MIDIShare contains all of the difficult code that performs the MIDI magic. That's great news for those like myself who wanted to get started in MIDI programming but didn't have a clue as to where to begin. I began a couple of years ago with MIDIShare after a blind search at yahoo on "MIDI API" brought up a link to the MIDIShare site.

The current version of JAVA from Sun Microsystems has support for MIDI. Apple's Quicktime Music Architecture also fully supports MIDI. I'm very excited about the potential of MIDI, JAVA and online music education. It's going to be very interesting. Below are some links that you also might find interesting. Well, I guess I'm thru.

MIDI related links (some may be no longer available)

Return to top of page


© Mike Sult