This lesson explores the expressive nuances of melody, including dynamics and articulation of the notes. We could play a series of notes in a steady quarter note rhythm but by changing the duration of notes from quarter notes to sixteenth notes we'd change the musical flow from legato to staccato. We'll explore the space between the notes. We'll also examine the velocity value that can be applied to each note (and which we have so far ignored). There are components that apply to the synths such as envelope and volume that also are important for expressive musical phrasing. This will allow us to create varying dynamics within a musical phrase. These additional parameters will result in the most complex code yet for creating melodies but it makes use of essential aspects of musical expression that were missing in previous examples.
We'll use our utility functions to help us with the rests. Tone.js doesn't recognize a duration of a rest but if we do some preprocessing of duration code we can add rests to our 'custom' duration language. We'll expand the duration code to include a duration such as "4nr" to mean a quarter note rest (or adding 'r' to any duration value). Tone.js will crash is you use a something with an 'r' appended for a duration so we can't simply add this to our duration code without processing that code before using it with Tone.js. The basic idea is to be able to have a duration code such as ["4n","4n","16n","16nr+8nr","16n","16nr+8nr"]; and the result will be a steady quarter note rhythm with the first two notes having a duration of a quarter note and the last two notes have a duration of a 16th note (like a staccato quarter note). Two different processing functions are used. processDurationNotation(duration_array) will return the start times of the different notes by accumulating the rests following a note duration and calculating when the next valid duration's start time is. The other function removeRestsFromDurations(duration_array) will do as its name says and remove any durations that have "r" in their code, so remaining are only the duration of the notes. Our custom duration code will now be translated into two arrays. One array has the start times of the notes (the 'time' parameter used in the callback), the other array has the durations of the notes played at those start times (the 'value.duration' parameter used in the callback).
Every note can have a velocity value. On a MIDI keyboard the velocity value is a measure of how fast you depress the key. It is usually mapped to volume dynamics (although it doesn't have to be used that way). The range in value for MIDI velocity is 0 to 127 but Tone.js uses a range of 0 to 1.0 so we'll adjust accordingly.
For our purposes velocity IS used for dynamics with low velocity being a quieter volume compared to a higher velocity value which results a louder volume. Another parallel array is used for our velocity value of the notes. We'll use the function mergeDurationVelocityAndPitch(durations, pitches, velocity) to create an array of Objects, each which has four properties: pitch, duration, time, and velocity. The callback function of the Tone.Part code will use these Objects. Now each note can have it's independent start time, duration and velocity.
Below is a simple example of this approach. The simple scalar part is in constant quarter note rhythm, the first time through using quarter note durations of varying velocity, the second time through using staccato quarter notes (actually sixteenth notes durations with rests after the note) with each note having its own velocity value. Below are the arrays before processing. Notice the velocity values near the end which change from 0.1 to 1.0 from one note to the next note.
var notes = ["A3","B3","C4","D4", "E4","D4","C4","B3", "A3","B3","C4","D4", "E4","D4","C4","B3",
"A3","B3","C4","D4", "E4","D4","C4","B3", "A3","B3","C4","D4", "E4","D4","C4","B3"];
var durs = ["4n","4n","4n","4n", "4n","4n","4n","4n", "4n","4n","4n","4n", "4n","4n","4n","4n",
"16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr",
"16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr",
"16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr",
"16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr","16n","16nr+8nr"];
var vel = [0.1,0.2,0.3,0.4, 0.5,0.6,0.7,0.9, 1.0,0.9,0.8,0.7, 0.6,0.5,0.4,0.3,
0.1,0.2,0.3,0.4, 0.5,0.6,0.8,1.0, 1.0,0.1,1.0,0.1, 0.1,1.0,0.1,1.0];
The volume of the synthesizer is an expressive element we need to have at our command. The volume can be changed dynamically using the synth.volume.setValueAtTime(value,time) and synth.volume.linearRampToValueAtTime(value,time) and other functions. These functions are from the Tone.js Core element Param and their use is explained in the documentation for Param.
An important point in using these functions is that a plus sign (+) needs to be prepended to the transport and/or notation code used for the time parameter of the setValueAtTime() and linearRampToValueAtTime() functions. Here we use these function to create crescendos and diminuendos on sustained tones.
function volumeDemo() {
var longToneNotes = ["A3","E4","A4","C#5","C5"];
var longToneDurs = ["1m","1m","1m + 2n + 4n","8n","2*1m + 8n"]
var longToneVels = [0.7,0.8,0.8,0.6,0.9];
var longTones = Rhythm.mergeDurationVelocityAndPitch(longToneDurs, longToneNotes, longToneVels);
console.log(longTones);
// bpm.value needs to be set before Tone.Part is created so that
// the definition of '1m' syncs with linearRampToValueAtTime()
Tone.Transport.bpm.value = 60;
var synth = new Tone.Synth().toMaster();
var volumePart1 = new Tone.Part(function(time, value){
synth.triggerAttackRelease(value.note, value.duration, time, value.velocity)
}, longTones ).start(0);
// create volume crescendos
synth.volume.setValueAtTime(-30, "0");
synth.volume.linearRampToValueAtTime(0, "+1m");
synth.volume.linearRampToValueAtTime(-30, "+1m + 32n");
synth.volume.linearRampToValueAtTime(0, "+2*1m");
synth.volume.linearRampToValueAtTime(-30, "+2*1m + 32n");
synth.volume.linearRampToValueAtTime(0, "+3*1m");
// fade away
synth.volume.linearRampToValueAtTime(-5, "+5*1m");
synth.volume.linearRampToValueAtTime(-Infinity, "+6*1m");
Tone.Transport.start("+0.1");
}
The ADSR (Attack Decay Sustain Release) envelope of a synthesizer will help determine the musical character of a music phrase. A slower Attack time can soften the sound of a phrase where as a fast Attack and Release time can create a harder edged sound. Here we we simply compare a musical phrase being played by different synths which have considerably different envelope settings.