In musical programming, sequencing refers to programming sequences of musical notes and modulations. Regardless of whether you are composing with sound, light, or both, sequencing is often a critical component of time-based compositions. Gibber has a simple philosophy for sequencing:
'Everything is a sequence'1. In practice, this means that any property or method of any Gibber object can easily be sequenced. Let's look at an example of sequencing the frequency of a Sine
wave. To start, we'll create a Sine oscillator with a frequency of 220 Hz. After changing the frequency
property of the oscillator, you will hear a corresponding change in pitch, as demonstrated in the following two lines of code:
a = Sine({ frequency:220 })
a.frequency = 880
If we want to change the frequency property at pre-defined intervals, we can use the seq
method of the frequency
property on our Sine
object. In the following example, the oscillator will switch between two frequencies every 250 ms:
a = Sine()
a.frequency.seq( [440, 880], ms(250) )
In the above code, we pass the seq
method an array of values to sequence, and a single duration. We can also pass an array of durations to loop through. Let's consider changing the rotation of a 3D Knot
geometry:
a = Knot()
a.rotation.seq( [0, Math.PI / 2, Math.PI, Math.PI * 1.5], [ ms(100), ms(500), ms(1000) ] )
Note that in the above example, there are more values than durations. Upon reaching the end of either the values or durations array, the sequencer simply loops back to the beginning of that particular array.
Again, it's important to note that every property and method has the seq
method available to it. This means that, in effect, each property / method is also its own object. Property objects in Gibber possess both the abstraction for sequencing that was just described, as well as a novel abstraction for creating multimodal, continuous mappings between properties that will be discussed in the next chapter. Method objects only possess the abstraction for sequencing. As an example of sequencing a method, consider the note
method of the Synth
object. It triggers a musical note at a provided frequency, with a corresponding amplitude envelope.
a = Synth()
a.note( 440 )
a.note( 880 )
The note
method can be sequenced in the same way we sequenced properties earlier.
a = Synth()
a.note.seq( [220,440,880,1760], [1/4,1/8,1/16,1/2] )
You can easily chain multiple sequences together; any call to the seq
method of a property/method returns the object containing the property. For example, Sine().frequency.seq( [440,880], 1/4)
would return the constructed Sine
oscillator. I typically indent chained sequence calls for code readability:
a = Drums('x*ox*xo-')
.pitch.seq( [.5,1,2,4], 1/8 )
.pan.seq( [-1,0,1], 1/8 )
.shuffle.seq( null, 1 )
In the above example, four sequences are created. The first, a sequence of the note
method, is actually created by default using the string passed to the Drums
constructor. The second, sequencing pitch
, changes the speed that the drum samples are played at every 1/8th note. In the third, we change the pan
of the drums every 1/8th note while in the final sequence, we call the shuffle
method every measure, which randomizes the order of the original note
sequence, creating changes in pattern.
A 3D geometry could be similarly sequenced:
a = Cube()
.rotation.seq( [.5,1,2,4], 1/8 )
.position.x.seq( [-50,0,50], 1/8 )
.scale.seq( [.5,1,2,4], 1/8 )
In the above example, we sequence the rotation
of the cube along all three axes, the position
of the cube along the x axis and the scale
of the cube along all three axes.
Every property / method of Gibber objects has a corresponding seq
method that can be used to easily sequence it.
1: Hat tip to grrrwaaa for this phrase