Play Rhythms

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

On this page we'll explore some of the ways one can enter rhythms into Tone.js. There are several ways to enter durations and rhythm using Tone.js. We'll explore three ways of entering rhythm for notes.

  1. TIME in seconds.
  2. Notation code: 1n, 2n, 4n, 8n = whole note, half note, quarter note, eighth note respectively.
  3. Transport Time code in the form of Measure : Beat : Subdivision (16th note) (example 1:1:2). Since the fields are zero indexed the Transport code of 1:1:2 means measure 2, beat 2, 3rd sixteenth.

Both Notation and Transport Time code will also depend on the tempo setting.


Time in seconds

First let's look at using TIME in seconds. As musicians, we don't usually think of musical time in seconds. Instead we think in terms of note duration values or measures and beats within those measures. The next two sections will cover that aspect of musical time. For this section we'll discuss using time in the units of seconds (or milliseconds). It is common for delay devices used with electric instruments to have a delay value measured in milliseconds. We'll experiment with a delayed signal mixed together with the original signal by setting up two identical Tone.Patterns and have a time delay control over one of the patterns.

The delay time is applied to the second Tone.Pattern's start time. As you change the Delay menu, you'll need to stop and restart to hear the new delay setting played correctly. The tempo is hard wired to 120 with a quarter note flow (quarter = 0.5 seconds or 500ms) and a C major scale is set up using Tone.Pattern with the alternateDown pattern. This pattern is sometimes called 'broken thirds' and is a common melodic contour that many musicians use with their practice of scales.

Some interesting delay settings at this tempo are 750ms and 800ms, the delayed part's first note will occur in between the second and third note of the original, creating a composite rhythm of eighth notes (at 750ms) with an interesting counterpoint of cascading melodic contour. Also the delay setting of 500ms (eight note for this tempo) creating an interesting chain of intervals of 2nds and 3rds as the notes leapfrog over one another.


function playDelay(){
    var melody = ["C4","D4","E4","F4","G4","A4","B4","C5"];
    var timeMenu = document.getElementById("delay");
    delayTime = Number(timeMenu.options[timeMenu.selectedIndex].value)/1000;

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

    // same as above except for .start(delayTime) instead of .start(0)
    var synth2 = new Tone.Synth().toDestination();
    var pattern2 = new Tone.Pattern(function(time, note){
    //the order of the notes passed in depends on the pattern
    synth2.triggerAttackRelease(note, "4n", time);
    }, melody, "alternateDown").start(delayTime);    
    
    var tempo = 120;
    Tone.Transport.bpm.value = tempo   
    Tone.Transport.start("+0.1");
}

Delay:
(click the stop button between plays)

In addition to delaying a signal in this manner there are Effects in Tone.js with much more sonic manipulating capabilities. We'll explore them in a later lesson.

An interesting time warping technique is used in one of the examples from the Tone.js site. In this example two identical melodic parts are panned left and right. The left channel use the default playbackRate (1.0) while the right channel has this line:


partR.playbackRate = 0.985;

An animation follows the pace of each part and just like Daytona, eventually partR gets 'lapped'. During the 'race' some interesting interactions occur between the two parts each marching to their own drummer..


Notation code

Another method of dealing with rhythm is to use notation code. We can use code similar to the Play Scale page but along with defining the scale array we'll also define the durations for each note in the scale. Instead of using Tone.Pattern, we'll use Tone.Part.

Tone.js can interpret notation code in the following form:

However you can't simply use notation code as if they are durations in the Tone.Part() function. Tone.js will interpret an array like [['4n, 'C4'],['4n','C#4'],['4n','D4'],['4n','D#4']] as four notes played at the same time (one quarter note duration from start(0)), however it will only play the first note. You can use a Tone.Time object to help, with something like the following. But it might not play what you expected. Although the notation code looks like it might be durations (2n = half note, 4n = quartet note), they are really time units and they will be used to create the time parameter used with Tone.Part(). The time parameter is the starting time of the note and the duration is something different. The following code WON'T create a quarter note C followed by a half note C# followed by a quarter note D followed by a half note D#. Instead the notation code is defining when the next note will begin and the duration is hard-coded as a quarter note ('4n') via the second parameter in the synth.triggerAttackRelease() function.


var t = new Tone.Time('0');

var notes =  [[t.add("4n").toNotation(), 'C4'],[t.add("2n").toNotation(),'C#4'],[t.add("4n").toNotation(),'D4'],[t.add("2n").toNotation(),'D#4']];

var synth = new Tone.Synth().toDestination();
var part = new Tone.Part(function(time, note){
    synth.triggerAttackRelease(note, "4n", time);
}, notes).start(0);

The start times aren't even what you might expect. We initiaiized t to Tone.Time('0'), but we called t.add('4n'),toNotation() before we paired that time with note C4, so that note will play a quarter after the start time. Then the second note, instead of playing on the next beat and lasting a half note, the [t.add("2n").toNotation(),'C#4'] array member is actually saying 'wait a half note before starting the C#'. So it seems that the first note was the half note. This was my initial misunderstanding of what notation code was about. It isn't duration of the notes when used this way but instead it is determining the start time of the next note. So a little more thought is needed, but notation code CAN be used to create a note's duration. This involves a little deeper dive into the parameters of synth.triggerAttackRelease();

NOTE: as I learn more about Tone.js I see that in some functions ( i.e. .setValueAtTime() ) you can use a value like '+4n', the plus sign has some magic powers, meaning 'from the current time'. This feature is useful when we later work with live updates of a running melody loop. [end NOTE]

In order to use the duration parameter we'll need to create a javascript object that has three attributes, a note, a duration. a start time. The sample code in documentation for Tone.Part shows the following: (the time value is using the Transport notation, discussed later.):


//use an array of objects as long as the object has a "time" attribute
var part = new Tone.Part(function(time, value){
	//the value is an object which contains both the note and the velocity
	synth.triggerAttackRelease(value.note, "8n", time, value.velocity);
}, [{"time" : 0, "note" : "C3", "velocity": 0.9}, 
	   {"time" : "0:2", "note" : "C4", "velocity": 0.5}
]).start(0);

In the function synth.triggerAttackRelease() we're not concerned with using a velocity value right now but we do want the use the duration field (currently hard-coded above with '8n'). It turns out the velocity parameter is optional, but we can access the duration value we need using a modified version shown below:


//use an array of objects as long as the object has a "time" attribute
var part = new Tone.Part(function(time, value){
	//the value is an object which contains both the note and the duration
	synth.triggerAttackRelease(value.note, value.duration, time);
}, [{"time" : 0, "note" : "C3", "duration": "2n"}, 
	   {"time" : "0:2", "note" : "C4", "duration": "2n"}
]).start(0);

We'll also modify the time parameter to use notation code instead of transport code. But either way we'll have to do some calculations. Tone.Time provides some functions (i.e. Tone.Time.add() ) to help us do the necessary music math. For the remainder of this page we'll need to write some helper functions to complete the duration notation (and also the next section's transport notation). Best practice is to put these functions in a separate javascript module. Then using a script tag import that module into this page. We'll use that technique for this page. Then we'll call the utility functions to create the code we need as parameters for the Tone.Part function.

The first function processDurationNotation() will take as a parameter an array of notation code and will return an array of the cummlative durations that is needed for the time attribute of the noteObjects we'll create in a second function mergeDurationsAndPitch() This second function mergeDurationsAndPitch() will return an array of noteObjects which will be used as a parameter to Tone.Part(). It will be similar in format to the example code shown above (which contains only two elements in the array of noteObjects) but it will be an named array (of arbitrary length) such as myMelody. The result is that we have the rhythms we want and each note has its own duration.

NOTE: The mariaPitches[] and mariaDurations[] arrays were transcribed from the sheet music to West Side Story.


var mariaPitches = ["Eb4","A4","Bb4","Eb4","A4","Bb4","C5","A4","Bb4","C5","A4","Bb4","Bb4","A4","G4","F4","Eb4","F4","Bb4","Ab4","G4","F4","Eb4","F4","Eb4","G4"];

var mariaDurations = ["8n","8n","2n + 4n","8n","4t","4t","4t","4t","4t","4t","8n","2n + 4n","8n","8n","8n","8n","8n","4n + 8n","8n","8n","8n","8n","8n","4n","4n","2n"];

// processDurationNotation() is called inside mergeDurationsAndPitch()
var myMelody = mergeDurationsAndPitch(mariaDurations, mariaPitches); 

//use an array of objects as long as the object has a "time" attribute
var part = new Tone.Part(function(time, value){
	//the value is an object which contains both the note and the duration
	synth.triggerAttackRelease(value.note, value.duration, time);
}, myMelody).start(0);

View source for details (best practice is to create a separate javascript module containing the utility functions but for source viewing on this demo page I've put these functions (commented out) in a script tag in the head section of this page). Click the 'Play Maria' button to hear the result of the notation code processing.

(click the stop button between plays)


Transport Time Code

The Tone.Transport code is another method of defining rhythm in Tone.js.

This doesn't really establish the duration of the note only it's start time. If we want a specific duration for each note we'll have to do something similar to the duration notation example above, i.e. create noteObjects that have the three attributes note, duration, start time (now in transport notation). For this example we don't need specific durations for each note as we will be creating a simple drum machine and each drum note can use the same duration. We'll concentrate on creating the transport notation to create the rhythm.

For the sake of experimentation, we take a different approach and use numbers as our strategy for creating rhythms. The numbers 24, 48, and 98 are useful for creating rhythms. If a quarter note was defined as having 24 'ticks' (subdivisions), then many of the other common faster durations will also be whole numbers.

Using 48 or 96 as the number of ticks per quarter note will give even more rhythmic precision. For our purposes we'll use the 24 ticks per quarter notes precision. Tone.js doesn't respond to these numbers, it's an intermediate step of our own to create the code that Tone.js does understand.

We can assemble an array of numbers representing the above durations and process them to create transport notation. Our javascript module of utility functions contains getTransportCode() and createTransportTimeCode which will create transport time code from an arrayOfNumbers parameter. We'll use that function to process the number arrays which are selected from the menu choices of our simple drum machine. Each menu choice contains as its value a series of numbers that equate to the rhythm described on the menu. That number array is then processed with getTransportCode() and the returned Transport Time code is used to create the Tone.Part for that instrument. The duration and pitch of the kick, snare and hihat instruments are constants. The conga part uses only three pitches. This example is all about the rhythm.

Selected different rhythms from the menus below and click 'Play Rhythms'. Use the mute checkbox to mute different parts while playing, also adjust the volume of different parts and experiment with tempo. If you change the rhythm menu while it's playing, you'll need to click Stop then the Play button again to hear the newly selected rhythm. With repeated clicking of Play without an intervening Stop, usually chaos ensues and to restore normalcy, you'll need to click stop and start again.


| volume:
Kick Drum patterns:
| volume:
Snare Drum patterns:
| volume:
Conga patterns:
| volume:
HiHat patterns:

tempo: (click the stop button between plays)

Below are some links to more information about Tone.js and rhythm.

Back to the Tone.js Setup page.