Play Major Scale

The music theory content can be found at Music Fundamentals on the Web.

We now need to play a sequence of notes we call a Scale. Our first scale is the major scale and we'll start in the key of C as our first example. Perhaps contrary to one's expectation in the study of music we don't start with the letter A. The musical language is "C centric", which means that C is the starting point of music studies, and the starting point for numbering the different octaves in the musical range of notes. The notation system is visually centered on the note C (middle C) and in a vertical mirror image the C notes are landmarks in the staff layout. (two ledger lines above the treble clef staff is C, two ledger lines below the bass clef staff is C.) So we will begin with creating a C scale.

Tone.js provides several way creating streams of notes. The Tone.Event class and it's descendants are useful for playing scales.

For our purposes on this page we'll use Tone.Pattern. We can create an array of notes for our major scale and Tone.Pattern will play them one after another. It can additionally plays those notes in a pattern such as upDown, downUp, etc. The full list of patterns is shown below.

The alternateUp and alternateDown patterns are different than the way most musicians would play those patterns. We'll learn how to make our own custom patterns in later lessons.

We can use an array for the note values of a major scale. The array uses bracket notation to define the beginning and end of the array. The note values inside the array use quotes and each value is separated by a comma.


var Cmajor = ['C4','D4','E4','F4','G4','A4','B4','C5'];

This is known as an array literal. You could write one for each key. Here's one for the key of Eb major.


var Ebmajor = ['Eb4','F4','G4','Ab4','Bb4','C5','D5','Eb5'];

Tone.js provides several ways to play these scale. We'll use the Tone.Pattern method. You can pass the Tone.Pattern function an array of notes (our major scale) and it will use a callback function to play those note one after another. Below is the code for playing the C major scale. The code for playing the Eb major scale is similar but with a different scale array defined.


function playCMajorScale(){
   var synth = new Tone.Synth().toDestination();
   var myScale = ['C4','D4','E4','F4','G4','A4','B4','C5'];
   var patternMenu = document.getElementById("melodicPattern");
   var patternName = patternMenu.options[patternMenu.selectedIndex].value;

   var pattern = new Tone.Pattern(function(time, note){
   //the order of the notes passed in depends on the pattern
   synth.triggerAttackRelease(note, "4n", time);
   }, myScale, patternName).start(0);    

   var tempo = document.myForm.tempo.value;
   Tone.Transport.bpm.value = tempo   
   synth.volume.value = document.myForm.volume.value;
   Tone.Transport.start("+0.1");
}
tempo: | Scale pattern: | volume:



You can click a play button more than once and you'll hear the scale play in harmony with itself. Set the pattern to random and click a button a couple of times to hear some interesting random harmony. Or click both the C and Eb buttons to hear the scale being played in two keys at once.

It wouldn't take too long to write out all of the major scales like we did for C and Eb major. But a better method is to write some code that will create the major scales using the rules of music theory.

The construction of a major scale use two rules:

Rule 1
adhere to the scale pattern for adjacent intervals: 0 2 2 1 2 2 2 1 where 0 = root and each number is the distance (in half steps) to the next note. An alternate way of looking at the formula is 0 2 4 5 7 9 11 12 where 0 = root of the scale and the other number are the offsets (in half steps) from the root (the cummulation of numbers from the interval pattern above). (We'll use the second method)
Rule 2
use all of the letter names of the music alphabet in some form (natural, sharp or flat) but only use each letter once per octave. For example don't use both F and F# in the same major scale. ALSO: for major scales there is never a mixture of sharps and flats in the same scale i.e.
D major = D E F# G A B C# (D) (no flat names used)
Db major = Db Eb F Gb Ab Bb C (Db) (no sharp names used)
(even though Gb and F# are the same sound, for Db major the correct name is Gb (not F#), because the letter F has already been used)
Caveats: The illegal root names for major scales
G#, D#, A#, E#, B# and Fb are NOT used as the starting note for major scales due to overly complex note names when following the rules (they result in double sharps or double flats) Instead use the following enharmonic keys respectively. (Ab, Eb, Bb, F, C, E)

A very easy way to work with musical pitches that can be played on a keyboard is to use MIDI numbers. The MIDI system assigns numbers to all of the keys of a keyboard. MIDI's (theoretical) keyboard has 128 keys! Middle C is assigned with the MIDI number of 60. We can use a literal array similar to the previous arrays but use numbers instead of pitch values to define the interval structure of a major scale.


var MAJOR = [0,2,4,5,7,9,11,12];
// Since these are numbers we don't use quotes.

Then using a loop we can create the MIDI key number values for a C major scale.


var MAJOR_SCALE = [0,2,4,5,7,9,11,12];
var startingNote = 60; // middle C
var Cmajor = [];
for(var i=0; i<MAJOR_SCALE.length; i++) {
    Cmajor.push( MAJOR_SCALE[i] + startingNote );
}
// Cmajor contains [60,62,64,65,67,69,61,72]

Now Cmajor contains the MIDI key numbers for a C major scale. But Tone.js doesn't speak the language of MIDI key numbers, they need to be translated into pitch-octave names or frequencies. Fortunately an easy solution is available by using an array of pitch-octave names the same size as the number of MIDI key numbers. (This solution comes from the book Making Music with Computers by Bill Manaris and Andrew R. Brown, highly recommended) The MIDI number is used as an index for this array and the value at that index is the pitch-octave translation that can be used with Tone.js or for any MIDI key number to pitch-octave translation


var MIDI_NUM_NAMES = ["C_1", "C#_1", "D_1", "D#_1", "E_1", "F_1", "F#_1", "G_1", "G#_1", "A_1", "A#_1", "B_1",
                "C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
                "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
                "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
                "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
                "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
                "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
                "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
                "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
                "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
                "C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9"];

var MAJOR_SCALE = [0,2,4,5,7,9,11,12];
var startingNote = 60;  // middle C
var myScale = [];
for(var i=0; i<MAJOR_SCALE.length; i++) {
    myScale.push( MIDI_NUM_NAMES[MAJOR_SCALE[i] + startingNote] );
}
// myScale contains ['C4','D4','E4','F4','G4','A4','B4','C5']

Now myScale contains the pitch-octave notes suitable for use with Tone.js. This will play the correct sounds for the C major scale and it will also have the correct names listed in the array. But if you look at the MIDI_NUM_NAMES array you'll notice there aren't any flat names used. All of the different 12 tones per octave sounds are in the array but all of the 'black keys' are named using sharp names.

If all we want to do is to play the correct sounds and don't care about note names, this approach will work for any key including the key of Eb. We simply change the starting note from 60 to 63 (Eb is 3 half steps above C).


var MAJOR_SCALE = [0,2,4,5,7,9,11,12];
var startingNote = 63;  // Eb above middle C
var myScale = [];
for(var i=0; i < MAJOR_SCALE.length; i++) {
    myScale.push( MIDI_NUM_NAMES[MAJOR_SCALE[i] + startingNote] );
}
// myScale contains ['D#4','F4','E4','G#4','A#4','C5','D5','D#5']
// it will create the correct sounds but it is the wrong spelling for an Eb major scale.

We have a problem with the spelling of the scale. The second rule listed above is being broken and since the MIDI_NUM_NAMES array doesn't have any flat names this method can't be used to get the correct names for major scales that use flat names. In order to get the correct name we need to have the enharmonic equilvalents as choices (i.e. D# or Eb). One approach is to have another array that contains the missing names in it. In addition we need to have an array of the natural letters so that we can keep track of which names are used and which letter name should be used next. We'll address the details of how we can fix this problem in a moment.

But first looking to the future when we learn more scales, we can make our scale creating loop more versatile if we don't hard code in the MAJOR_SCALE formula. But instead allow a different scale formula to be used. A different scale formula could be assigned to myScaleFormula and the loop code could be used without any changes. This change is useful for turning our "scale creation" code into a function.


var MAJOR_SCALE = [0,2,4,5,7,9,11,12];

var myScaleFormula = MAJOR_SCALE;
var startingNote = 64;  // E above middle C
var myScale = [];
for(var i=0; i < myScaleFormula.length; i++) {
    myScale.push( MIDI_NUM_NAMES[myScaleFormula[i] + startingNote] );
}
// myScale contains ['E4','F#4','G#4','A4','B4','C#5','D#5','E5']
// E major scale which uses sharps is spelled correctly.

Here is one strategy for enforcing the second rule. First we'll create two arrays similar to MIDI_NUM_NAMES (but slightly different). These two arrays contain all of the names we need for major scales.


var MIDI_SHARP_NAMES = ['B#_0',  'C#_1', 'Cx_1', 'D#_1',   'E_1',  'E#_1',  'F#_1', 'Fx_1',  'G#_1', 'Gx_1', 'A#_1', 'B_1',
                    'B#_1', 'C#0', 'Cx0', 'D#0', 'E0', 'E#0', 'F#0', 'Fx0', 'G#0', 'Gx0', 'A#0', 'B0',
                    'B#0', 'C#1', 'Cx1', 'D#1', 'E1', 'E#1', 'F#1', 'Fx1', 'G#1', 'Gx1', 'A#1', 'B1',
                    'B#1', 'C#2', 'Cx2', 'D#2', 'E2', 'E#2', 'F#2', 'Fx2', 'G#2', 'Gx2', 'A#2', 'B2',
                    'B#2', 'C#3', 'Cx3', 'D#3', 'E3', 'E#3', 'F#3', 'Fx3', 'G#3', 'Gx3', 'A#3', 'B3',
                    'B#3', 'C#4', 'Cx4', 'D#4', 'E4', 'E#4', 'F#4', 'Fx4', 'G#4', 'Gx4', 'A#4', 'B4',
                    'B#4', 'C#5', 'Cx5', 'D#5', 'E5', 'E#5', 'F#5', 'Fx5', 'G#5', 'Gx5', 'A#5', 'B5',
                    'B#5', 'C#6', 'Cx6', 'D#6', 'E6', 'E#6', 'F#6', 'Fx6', 'G#6', 'Gx6', 'A#6', 'B6',
                    'B#6', 'C#7', 'Cx7', 'D#7', 'E7', 'E#7', 'F#7', 'Fx7', 'G#7', 'Gx7', 'A#7', 'B7',
                    'B#7', 'C#8', 'Cx8', 'D#8', 'E8', 'E#8', 'F#8', 'Fx8', 'G#8', 'Gx8', 'A#8', 'B8',
                    'B#8', 'C#9', 'Cx9', 'D#9', 'E9', 'E#9', 'F#9', 'Fx9'];
                          

var MIDI_FLAT_NAMES = ['C_1', 'Db_1', 'D_1', 'Eb_1', 'Fb_1', 'F_1', 'Gb_1', 'G_1', 'Ab_1', 'A_1', 'Bb_1', 'Cb0',
                    'C0', 'Db0', 'D0', 'Eb0', 'Fb0', 'F0', 'Gb0', 'G0', 'Ab0', 'A0', 'Bb0', 'Cb1',
                    'C1', 'Db1', 'D1', 'Eb1', 'Fb1', 'F1', 'Gb1', 'G1', 'Ab1', 'A1', 'Bb1', 'Cb2',
                    'C2', 'Db2', 'D2', 'Eb2', 'Fb2', 'F2', 'Gb2', 'G2', 'Ab2', 'A2', 'Bb2', 'Cb3',
                    'C3', 'Db3', 'D3', 'Eb3', 'Fb3', 'F3', 'Gb3', 'G3', 'Ab3', 'A3', 'Bb3', 'Cb4',
                    'C4', 'Db4', 'D4', 'Eb4', 'Fb4', 'F4', 'Gb4', 'G4', 'Ab4', 'A4', 'Bb4', 'Cb5',
                    'C5', 'Db5', 'D5', 'Eb5', 'Fb5', 'F5', 'Gb5', 'G5', 'Ab5', 'A5', 'Bb5', 'Cb6',
                    'C6', 'Db6', 'D6', 'Eb6', 'Fb6', 'F6', 'Gb6', 'G6', 'Ab6', 'A6', 'Bb6', 'Cb7',
                    'C7', 'Db7', 'D7', 'Eb7', 'Fb7', 'F7', 'Gb7', 'G7', 'Ab7', 'A7', 'Bb7', 'Cb8',
                    'C8', 'Db8', 'D8', 'Eb8', 'Fb8', 'F8', 'Gb8', 'G8', 'Ab8', 'A8', 'Bb8', 'Cb9',
                    'C9', 'Db9', 'D9', 'Eb9', 'Fb9', 'F9', 'Gb9', 'G9'];

Also we need a new array of the alphabetic letter names used in the music language so can make sure we have the correct letter names for our major scale.


var ALPHA_NAMES = ['A','B','C','D','E','F','G'];
var startingName = "Eb4"
var offset;
for(var i=0; i < ALPHA_NAMES.length; i++) {
    if(startingName.includes(ALPHA_NAMES[i])) {
        offset = i;
        break;
    }
}
var MAJOR_SCALE = [0,2,4,5,7,9,11,12];
var startingNote = 63;  // Eb above middle C
var myScaleFormula = MAJOR_SCALE;
var myScale = [];
for(var i=0; i < myScaleFormula.length; i++) {
    if(MIDI_SHARP_NAMES[myScaleFormula[i] + startingNote].includes(ALPHA_NAMES[(offset+i) % ALPHA_NAMES.length])) {
        myScale.push( MIDI_SHARP_NAMES[myScaleFormula[i] + startingNote] );
    } else if(MIDI_FLAT_NAMES[myScaleFormula[i] + startingNote].includes(ALPHA_NAMES[(offset+i) % ALPHA_NAMES.length])) {
        myScale.push( MIDI_FLAT_NAMES[myScaleFormula[i] + startingNote] );
    } else {
        myScale.push("C7"); // high note used to indicate error
    }
}

The line of code inside the last loop;

if(MIDI_SHARP_NAMES[myScaleFormula[i] + startingNote].includes(ALPHA_NAMES[(offset+i) % ALPHA_NAMES.length]))

looks a bit complicated. Let's break it down.

The first part MIDI_SHARP_NAMES[myScaleFormula[i] + startingNote] will be one of the pitch-octave names from the MIDI_SHARP_NAMES array. Which one of the MIDI_SHARP_NAMES is determined by the index of that array. That value is the MIDI key number, myScaleFormula[i] + startingNote. So using that index into the array we receive a note name-octave (of type string). On that name we call the string method '.includes()' and pass in ALPHA_NAMES[(offset+i) % ALPHA_NAMES.length]

The index of the ALPHA_NAMES is (offset+i) % ALPHA_NAMES.length. The % (remainder) operator is used so that we don't go outside the bounds of the array. The offset value sets us at the correct starting point in the array, then we increment through the array with the i value. When we divide by the length of the ALPHA_NAMES array our remainder value will be a value within the bounds of the array and the offset gives us the correct starting point. It's a useful pattern that applies later when we create modes from a parent scale.

So we're looking to see if the note name we have in the MIDI_SHARP_NAMES array contains a specific Letter from the ALPHA_NAMES. If not then we check the MIDI_FLAT_NAMES array. One of the two if() else if() statements should be true and we choose our correct name from that array. But if it's still not found, that's an error. We don't want to crash (trainwreck crashes can cause horrible noises emanating from Tone.js), so we'll just add a high note (above the range we're working in) so that we can hear the error if something goes wrong.

Just one small step to turn this code into a function. We have a way to turn a MIDI number into a noteName+octave but it's also useful to go the other direction, i.e. turn a noteName+octave into a MIDI number. One brute force technique is to search through the two MIDI arrays and when you find it return the index of array of the found name. Below is a function that does that translation.


function noteNameToMIDI(noteName)  {
    var i;
    var MIDInumber = -1; // default if not found
    // check both arrays for the noteName
    for(i=0; i < MIDI_SHARP_NAMES.length; i++) {
        if( noteName == MIDI_SHARP_NAMES[i] ||
                noteName == MIDI_FLAT_NAMES[i] ) {
            MIDInumber = i;  // found it
        }
    }
    return Number(MIDInumber); // it should be a number already, but...
}

var MAJOR_SCALE = [0,2,4,5,7,9,11,12];

function makeScale(scaleFormula, keyNameAndOctave) {
	var ALPHA_NAMES = ['A','B','C','D','E','F','G'];
	var startingName = keyNameAndOctave;
	var offset;
	for(var i=0; i<ALPHA_NAMES.length; i++) {
		if(startingName.includes(ALPHA_NAMES[i])) {
			offset = i;
			break;
		}
	}
	var startingNote = noteNameToMIDI(keyNameAndOctave);
	var myScaleFormula = scaleFormula;
	var myScale = [];
	for(var i=0; i < myScaleFormula.length; i++) {
		if(MIDI_SHARP_NAMES[myScaleFormula[i] + startingNote].includes(ALPHA_NAMES[(offset+i) % ALPHA_NAMES.length])) {
			myScale.push( MIDI_SHARP_NAMES[myScaleFormula[i] + startingNote] );
		} else if(MIDI_FLAT_NAMES[myScaleFormula[i] + startingNote].includes(ALPHA_NAMES[(offset+i) % ALPHA_NAMES.length])) {
			myScale.push( MIDI_FLAT_NAMES[myScaleFormula[i] + startingNote] );
		} else {
			myScale.push("C7"); // high note used to indicate error
		}
	}
	return myScale;
}

var Ebmajor = makeScale(MAJOR_SCALE, 'Eb4');

Now that we have the makeScale function, we can use it to create scales that can be played by similar code as earlier hard coded examples. Choose a key and a pattern then click Play Major Scale

key:



tempo: | Scale pattern: | volume:

After you click the Play Major Scale button you'll see a display of the note names created. These are the names returned from the makeScale function and are used with the Tone.js function shown below. Notice the different spelling for the keys of C#/Db, F#/Gb and B/Cb. Each pair sounds the same but have completely different spelling. This music theory stuff can be tricky.


function playScale(){
   var synth = new Tone.Synth().toDestination();
   var keyNameMenu = document.getElementById('key');
   var keyName = keyNameMenu.options[keyNameMenu.selectedIndex].value;
   var myScale = makeScale(MAJOR_SCALE, keyName);
   document.getElementById("scaleDisplay").innerHTML = '<h2>'+myScale+'</h2>';
   var patternMenu = document.getElementById('melodicPattern2');
   var patternName = patternMenu.options[patternMenu.selectedIndex].value;

   var pattern = new Tone.Pattern(function(time, note){
   //the order of the notes passed in depends on the pattern
   synth.triggerAttackRelease(note, "4n", time);
   }, myScale, patternName).start(0);    

   var tempo = document.myForm2.tempo2.value;
   Tone.Transport.bpm.value = tempo   
   synth.volume.value = document.myForm2.volume2.value;
   Tone.Transport.start("+0.1");
}

This completes our first iteration of our scale creation code. We'll see if it can handle some other scale structures in later lessons. Best practice is have all this code placed into its own javascript file but for these demo pages I'm putting the code in script tags within this html page. View source for details.

Back to the Tone.js Setup page.