The higher level Tone.Event classes such as Tone.Loop, Tone.Pattern, Tone.Part and Tone.Sequence allow you to create a composition that can be started, stopped and use repeated sections etc. But those classes don't provide the easiest path for live updates of the notes once the Transport starts. However, Tone.js has the capabilities to play dynamic updates of notes, durations and other aspect of performance as a loop is playing by using some different coding techniques. One of the questions posted at the Tone.js google discussion group asked about a strategy for creating dynamic updates while a loop is playing. An interesting recursive coding technique was suggested as follows:
function scheduleNext(time){
//play note
//schedule the next event relative to the current time by prefixing "+"
Tone.Transport.schedule(scheduleNext, "+" + random(1, 3))
}
By having the 'play note' section draw its notes from an input field that the user can change as the tranport is playing, dynamic changes of the notes can be introduced. In the sample code above, it shows how the start time can be set to be a random time between 1 and 3 seconds. Very important is to prefix that time with '+' which means 'measured from the current 'now' time. Since the code has just played a note it is using Tone.Transport.schedule() to schedule the next note in relation to time of the note just played. This dynamic updating technique can also be extended to other aspects of the performance.
We won't use a random value but instead a user input value. We'll limit our exploration to adding durations to our list of dynamic elements. We'll set up the scheduleNext() function to read from a text field to get both the notes and the durations. Since this is a stream of notes without rests, the duration values also will determine the start time of the next note. Once the Tone.Transport is started scheduleNext() will be reading these text fields over and over and we need a way to stage the updates without typing directly into the field that the code is reading. If the code reads the field while we were typing, then it might read something that wasn't completely finished yet and cause an error. We have two ways to enter notes while the loop is running.
NOTE: the number of notes doesn't have to match the number of durations and some interesting rhythm/pitch loops are created with a mismatch of the length of notes vs. durations (as with the default setup). Whatever number of notes you enter it will loop through those pitches (in order) and it will assign each note a duration from the array of durations (in order of the durations).
Our code, shown below, simply fills out the details of the suggested sample code from the discussion group. The start button calls the playLiveUpdate() function and the update buttons call their respective update functions. The function scheduleNext() keeps recursively scheduling a new call to itself, until you click the stop button which stops Tone.Transport.
As the noteIndex and durationIndex values increase with each call to scheduleNext() the % (remainder) operator keep the number within the range of the array being looped over. The % (remainder) operator is a useful coding technique when looping through arrays of musical data. var myNote = notes[noteIndex % notes.length];
Since the user is typing data directly into the program there needs to be some validity check on that input. If the input passes the validity test of checkBackstageNotes() and checkBackstageDurations(), it becomes the new updated data otherwise no changes are made. Tone.js has helper functions for type checking. One of the functions, Tone.isNote(), checks for valid note/octave names. That makes the checkBackstageNotes() function very simple. Since there are several valid ways to enter durations the checkBackstageDurations() function is more involved. This page allows only duration notation (2n, 4n, etc.) in the backstageDuration field enforced by checkBackstageDurations().
var noteIndex = 0;
var notes = ['C4','E4','G4','B4'];
var durationIndex = 0;
var durations = ['8n','16n','16n','16n','16n'];
var synth = new Tone.Synth().toMaster();
var liveNotes = [];
var liveDurations = [];
// init
liveNotes = notes;
liveDurations = durations;
function playLiveUpdate() {
if(Tone.Transport.state != 'started') {
noteIndex = 0;
durationIndex = 0;
updateVolume();
updateTempo();
scheduleNext('+0.1');
Tone.Transport.start('+0.2');
}
}
function updateNotes() {
var newNotes = document.getElementById('backStageNotes').value;
if(checkBackstageInput(newNotes) == false) {
liveNotes = notes;
document.getElementById('backStageNotes').value = notes;
} else {
liveNotes = newNotes.split(',');
}
var shortList = [];
if(liveNotes.length > 4) {
shortList = liveNotes.slice(0,4);
shortList.push("...");
} else {
shortList = liveNotes;
}
document.getElementById('noteList').innerHTML = shortList;
}
function updateDurations() {
var newDurations = document.getElementById('backStageDurations').value;
if(!checkBackstageDurations(newDurations)) {
liveDurations = durations;
document.getElementById('backStageDurations').value = durations;
} else {
liveDurations = newDurations.split(',');
}
var shortList = [];
if(liveDurations.length > 4) {
shortList = liveDurations.slice(0,4);
shortList.push("...");
} else {
shortList = liveDurations;
}
document.getElementById('durationList').innerHTML = shortList;
}
The following environment is setup with a C major seventh chord (4 notes) and an array of 5 durations. Click the start button to hear the loop, then change some of the notes and/or durations and then click the update buttons. You'll hear the newly entered notes and duration played by the looping figure. The menu choices contain some other notes and rhythms. Also you can type in new set of notes or durations and when you press the return (enter) key it will be added to the menu. Remember that you have to click the update buttons to load in the new notes or rhythms.
Trying changing one note of the chord in the backstage then click update, then change another note as your create an arpeggiated chord progression. Remember to click update after each change as you're just preparing the changes in the backstage, they don't take effect until you click update. Experiment with different durations, sometimes with same number of durations as notes, sometimes with a mismatch in the number of durations vs. notes. Below are some other array of notes you can copy right off the page and paste into the 'Add a menu item' field. Or you can type in any notes you want into the 'Add a menu item' field. The same process is used for adding new rhythms, simply type the comma separated durations into the 'Add a menu item' field then press return to add it to the menu. Once you select a set of notes from the menu, click the update button to hear the changes.
c4,b3,c4,c#4,d4,d#4,e4,f4
d3,f3,g3,f3,d3,f3,d3,f3,g3,f3,a2,c3
C4,Eb4,Eb4,D4,D4,F4,F4,E4,E4,G4,G4,F4,F4,Ab4,Ab4,G4,G4,Bb4,Bb4,Ab4,Ab4,C5,C5,B4,B4,D5,D5,C5,C5
G4,Eb4,C4,Eb4,Ab4,Eb4,C4,Eb4,Ab4,A4,Eb4,C4,Eb4,Ab4,Eb4,C4,Eb4,F4,F#4
These techniques illustrate some interesting possibilities regarding how we can improvise in this new musical environment.