Play Intervals - part 2

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

In the previous iteration of our playInterval code we had some note naming issues. However once we get some note names, the playing of the notes is easy with the code below.


function playInterval(notes) {
    var synth = new Tone.Synth().toDestination();
    var interval = new Tone.Sequence(function(time, note){
        synth.triggerAttackRelease(note, 1);
    }, notes, "2n");

    //begin at the beginning
    interval.loop = false;
    interval.start(0);    
    Tone.Transport.start("+0.1");
}

We'll deal with the note naming issue in more detail on this page. It will be useful to use the alphabet array also used with makeScale() function


var ALPHA_NAMES = ['A','B','C','D','E','F','G'];

When we create an interval we can start with an interval number not in half steps size, but in interval number size, i.e. unison, 2nd, 3rd, 4th, 5th, 6th, 7th, and octave. These numbers originate with scale degree counting (not half step counting). Each of these interval number can come in different varieties such as minor 2nd, major 2nd, augmented 2nd. We refer to the "minor", or "major" part as the quality of the interval. The interval number will tell us the alphabet names to use, the interval quality will tell us if sharps or flats are needed. To create a makeInterval() function we'll need to implement both of these steps.

We want to allow the user to select which intervals they want to listen to for eartraining. So we present a series of chechboxes for unison, 2nd, 3rd, 4th, 5th, 6th, 7th, and octave. Let's assume the user selects '3rds'. We should limit the random number generator to numbers that could be the half step distances of a 3rd. A Major 3rd is 4 half steps, a Minor 3rd is 3 half steps. Augment 3rd (5 half steps) and diminished 3rd (2 half steps) are also possibilities but we'll leave them out for now to keep it more simple. Since it's a 3rd we should make sure the alphabet name of the second note skips a letter in ALPHA_NAMES array compared to the alphabet name of the first note. Then we should adjust those letter names with sharps or flats is necessary to creat the appropriate size in half steps. Also, just so we start out with some sanity let's reject B#, E#, Cb, Fb, and any name with double sharps as our first note. Additionally if the interval is a half step make sure the first note doesn't use a flat name because we'd have to have a double flat as the second note (assuming we are creating an interval of a minor 2nd). We don't want to deal with double flats at this point so that is why we pass in the intervalHalfSteps parameter to the getFirstName() function.


function getAlphaForInterval(firstNote, intervalNum) {
	var ALPHA_NAMES = ['A','B','C','D','E','F','G'];
    // find the first location
    var firstAlpha = firstNote.slice(0,1);
    for(let i=0; i<ALPHA_NAMES.length; i++) {
        if( ALPHA_NAMES[i].includes(firstAlpha) )
            return ALPHA_NAMES[(i+intervalNum) % ALPHA_NAMES.length];
    }    
}

function getFirstName(midiNumber, intervalHalfSteps) {
    let firstName;
    let whichArray = getRandomIntInclusive(0,1);
    if(whichArray === 0) {
        // reject double sharps E#, B#
        if(MIDI_SHARP_NAMES[midiNumber].includes("x") ||
             MIDI_SHARP_NAMES[midiNumber].includes("E#") || 
               MIDI_SHARP_NAMES[midiNumber].includes("B#") ) {
            firstName = MIDI_FLAT_NAMES[midiNumber];
        } else {
            firstName = MIDI_SHARP_NAMES[midiNumber];
        }
    } else {
        // reject Cb and Fb
        if(MIDI_FLAT_NAMES[midiNumber].includes("Cb") ||
              MIDI_FLAT_NAMES[midiNumber].includes("Fb") ) {
            firstName = MIDI_SHARP_NAMES[midiNumber];
        } else {
            firstName = MIDI_FLAT_NAMES[midiNumber];
            // reject flat names if the interval is 1 half step
            if(firstName.includes('b') && intervalHalfSteps === 1) {
                firstName = MIDI_SHARP_NAMES[midiNumber];
            }
        }
    }
    return firstName;
}

function getCorrectName(midiNumber, noteAlpha) {
    var correctName;
    if( MIDI_FLAT_NAMES[midiNumber].includes(noteAlpha) ) {
        correctName = MIDI_FLAT_NAMES[midiNumber];
    } else if( MIDI_SHARP_NAMES[midiNumber].includes(noteAlpha) ) {
        correctName = MIDI_SHARP_NAMES[midiNumber];
    } else {
        correctName = "F#7"; // play high F# for error. but don't crash.
    }
    return correctName;
}

function makeInterval(firstMIDI, intervalNum, intervalHalfSteps) {
    var intervalNotes = [];
    var firstName = getFirstName(firstMIDI, intervalHalfSteps);
    var secondAlpha = getAlphaForInterval(firstName, intervalNum);
    var secondNote = getCorrectName((firstMIDI+intervalHalfSteps), secondAlpha);
    intervalNotes.push(firstName);
    intervalNotes.push(secondNote);
    return intervalNotes;
}


// FROM: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
function getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

var intervalNotesSaved = ['C4','C4']; // default unison to start

// play button .onclick
function setupAndPlayInterval() {
    UpdateCheckboxList()
    var firstMIDINum = getRandomIntInclusive(60,71);
    var intervalSizeHalfSteps = createInterval();    
    var descendingIntervalCheckbox = document.getElementById("descendingInterval");
    if (descendingIntervalCheckbox.checked) {
        intervalSizeHalfSteps = intervalSizeHalfSteps * -1;
        currentIntervalNum = currentIntervalNum * -1;
    }
    var myNotes = makeInterval(firstMIDINum, currentIntervalNum, intervalSizeHalfSteps);
    intervalNotesSaved = myNotes; // save for possible replay
    playInterval(myNotes);
}

// replay button .onclick
function replayInterval() {
    if(intervalNotesSaved) {
        playInterval(intervalNotesSaved);
    }
}

Click the Create New Interval button and listen. Given the name of the first note, what to you think the name of the second note is? How many half steps are in the interval? Click the Show Note Names button to reveal the name of the second note. This version will give us more common note names and interval spelling. It's not complete but it's better than the previous version.

(click the stop button between plays)

Limit to only Intervals of:

Another approach to creating interval for eartraining is to selected two notes from any of the four scales that we've studied, Major, Natural Minor, Harmonic Minor, and Melodic Minor. With this approach we might have the user (optionally) select one of the scales and a key then write code to create an interval of two notes from that scale. That way we are putting the intervals into the context of a real scale. Our third iteration of this topic of intervals is discussed on Play Intervals part three.

Back to the Tone.js Setup page.