-
Notifications
You must be signed in to change notification settings - Fork 118
Playing A Melody
This tutorial follows on from part 2 of the user guide, and will probably make more sense if you have worked through that first.
In part 2 we got to the point where we could play single notes using a synth. Now it is time to use our synth to play a series of notes, creating a simple melody. Audiolet contains a simple callback-based scheduler which allows you to schedule events to happen at any point in the future. Events are scheduled according to how many beats have elapsed since playback started. So, to play our first simple melody, we can slightly expand our AudioletApp class.
...
var AudioletApp = function() {
this.audiolet = new Audiolet();
this.audiolet.scheduler.addAbsolute(0, function() {
var synth = new Synth(this.audiolet, 262);
synth.connect(this.audiolet.output);
}.bind(this));
this.audiolet.scheduler.addAbsolute(1, function() {
var synth = new Synth(this.audiolet, 262);
synth.connect(this.audiolet.output);
}.bind(this));
this.audiolet.scheduler.addAbsolute(2, function() {
var synth = new Synth(this.audiolet, 392);
synth.connect(this.audiolet.output);
}.bind(this));
this.audiolet.scheduler.addAbsolute(3, function() {
var synth = new Synth(this.audiolet, 392);
synth.connect(this.audiolet.output);
}.bind(this));
};
...
If you load this up, you should hear the first four notes of 'Twinkle, Twinkle, Little Star'. Looking at the code you can see that we use the first argument of the scheduler's addAbsolute function to specify the exact beat that we want the note to play on. The second argument is the callback, where we create and connect the synth. As well as being able to schedule notes on an exact beat, there is also an addRelative function, which schedules callbacks a number of beats in the future relative to the current playback position.
Whilst scheduling each note is simple, when it comes to creating more complex pieces of music it quickly becomes a huge task. To simplify complex scheduling jobs Audiolet uses Patterns.
Patterns are small classes which behave very simply. When the Pattern.next() function is called, they return the next value in a sequence, or a null object if the pattern is finished. So for example the PSequence pattern runs through an array of values a certain number of times.
> var a = new PSequence([2, 3, 5], 1);
> a.next();
2
> a.next();
3
> a.next();
5
> a.next();
null
> a.next();
null
By using the scheduler's play method, we can automatically grab the next value from a pattern and use it in a callback function. We can use this to rewrite our earlier code using a PSequence rather than scheduling each individual note.
...
var AudioletApp = function() {
this.audiolet = new Audiolet();
var frequencyPattern = new PSequence([262, 262, 392, 392], 1);
this.audiolet.scheduler.play([frequencyPattern], 1,
function(frequency) {
var synth = new Synth(this.audiolet, frequency);
synth.connect(this.audiolet.output);
}.bind(this)
);
};
...
The play function takes three arguments. The first argument is an array containing the patterns which we want to use. The second argument is the duration argument, which specifies how regularly we want the callback to be fired. Here we set the duration to be 1, meaning that we want a callback every beat. The third argument contains the callback function. You can see that it takes a single argument, corresponding to the first (and only) pattern in the list of patterns. If we had supplied two patterns the callback function would need two arguments.
Whilst patterns are often useful for playing through simple lists like we have seen up to this point, much of the power of patterns comes through the ability to embed them within each other, and chain them together. The following example uses this ability to randomly select melodies and rhythms to play back, forming a constantly changing (albeit directionless) tune.
...
var AudioletApp = function() {
this.audiolet = new Audiolet();
var melodyA = new PSequence([262, 294, 330, 349]);
var melodyB = new PSequence([349, 330, 349, 392]);
var melodyC = new PSequence([440, 392, 349, 330]);
var frequencyPattern = new PChoose([melodyA, melodyB, melodyC],
Infinity);
var durationPattern = new PChoose([new PSequence([4, 1, 1, 2]),
new PSequence([2, 2, 1, 3]),
new PSequence([1, 1, 1, 1])],
Infinity);
this.audiolet.scheduler.play([frequencyPattern], durationPattern,
function(frequency) {
var synth = new Synth(this.audiolet, frequency);
synth.connect(this.audiolet.output);
}.bind(this)
);
};
...
The PChoose patterns randomly pick a pattern from the array given in their first argument. As we give Infinity for the second arguments, the patterns will keep generating values, and never return null when their next() function is called by the scheduler. The other point to note in this example is that rather than passing a number to play function for the duration, we instead use a pattern. This means that rather that playing a note on every beat, we can create changing rhythms.
Now you have seen how to produce simple synths and play melodies using them, try playing around with changing the structure of the synths by adding further generators, modulators and envelopes, and start to explore the range of patterns which are built into Audiolet.