[thumbuki.com]

V. Step Sequencer

Coding notes one at a time is tedious work. Modeling an instrument on classic analogue step sequencers helps save time composing one i-statement at a time while adding the convenience of reusable patterns. This style of user interface also greatly influences the way a composer approaches the music. Step sequencers, by their very nature, are grounded in electronic music, and are a great asset for those writing techno music or any of its sister genres.

Basic Structure

Designing a basic step sequencer in Csound is a fairly simple process. The fundamental techniques used here are derived from the previous section Phasor, Table, and Oscillator.

Pitches, panning, envelopes, etc. are stored in f-tables. At playback, the specified tables are synced using a master clock, which is created from a single phasor. Figure 5.1 shows a four note sequence and various elements perfectly synced as the master phasor progresses through one cycle.

Figure 5.1    Several f-tables can be used in conjunction to control various aspects of a step sequencer.

Master Clock

Even in digital systems, drift is a problem due to internal rounding of floating-point numbers and other exciting issues of this nature. My earliest prototypes did not employ a master clock, which led some of my oscillators to eventually fall out of sync. This was an eye opening experience for me, as I had previously assumed that digital synthesizers did not suffer the same drawbacks as their analogue counterparts.

The master clock, aclock, is built from an a-rate phasor. The frequency for the phasor is set to 1/idur, which allows a group of f-tables, or pattern, to play from beginning to end over the duration of the current score instance. This gives the user the ability to change the tempo of a pattern by merely altering p3.

A k-rate child clock, kclock, is also created from aclock using the opcode downsamp. The reason this is necessary is because not all opcode parameters can use an a-rate variable as their argument. By copying aclock and converting this to a k-rate signal, synchronization between the two is guaranteed.

aclock phasor   1/idur
kclock downsamp aclock
Figure 5.2    The master clock.

Table Length

For a phasor to read through the length of a stored f-table, the signal must be scaled to the size of the f-table. The examples in the previous section hard-coded the table sizes into the instruments. There is a mechanism in Csound for retrieving the size of a table, ftlen.

The ftlen opcode accepts an f-table number for its argument and returns the size the table. Using this method over hard coding provides two distinct advantages. First, the composer will no longer need to alter instrument code if and when the size of the tables are changed. Second, the sequencer's design becomes more versatile, as it can automatically step through patterns of any length. In other words, there is no need to create an eight note sequencer separate from a sixteen note sequencer.

f1 0 8192 10 1
f2 0 16 -2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Figure 5.3    A 8192 point sine wave table and a table with 16 points of data.
isize1 = ftlen(1)
isize2 = ftlen(2)
Figure 5.4    ftlen sets isize1 to 8192 and isize2 to 16.
atable table aphasor * 8192, 1
Figure 5.5    The less compatible method for scaling a phasor signal.
itable =     1
isize  =     ftlen(itable)
atable table aphasor * isize, itable
Figure 5.6    This code is compatible with an f-table of any size.

Naming Variables

A great habit to develop for writing code is choosing effective names for variables. This practice can keep a Csounder from losing themselves in an ocean of their own design, while making his or her instruments easier to read by others. While we are given the freedom to name our variables after our pets or simply numbering them, a much more useful approach is to give them names that tells us something about what the variables do.

Within the step sequencer, I've created a local naming standard for the variables. After i, k, or a, I use three different letters to denote whether the variable is used as an f-table, length, or a table signal:
    ifenv =     ...
    ilenv =     ...
    atenv table ...
Figure 5.7    Example of using custom character codes to denote the separate parts of an envelope.

Voltage Controlled Oscillator

The oscillator in this step sequencer uses the vco opcode. The reason this opcode was chosen over oscil is because it has the classic waveforms built-in and, metaphorically speaking, fits the model of an analogue step sequencer. The vco also supports dynamic control of the pulse width and the shape of a triangle/saw/ramp waveform. iwave, or p5, is designated for choosing which waveform to use for an instance of the sequencer.

iwave = p5 ; 1 = sine, 2 = PWM, 3 = triangle/saw/ramp
Figure 5.8    A user can specify which waveform to use with p5 in the score.

Pitch

From this point forward, the sequencer will begin to take shape, as the step parameters of the sequencer are added one at a time, starting with pitch.

There are two different ways the table opcode is used in the sequencer. The first is cycling through an f-table once per instance. The second is cycling through an f-table as many times as there are steps per instance. The pitch table uses the former method, as it only needs to be read through once during an instance.

The pitches are stored in a GEN02 generated f-table. GEN02 allows users to individually specify each point of data in the table, which is well-suited for picking the notes. The fourth parameter is set to -2, which tells GEN02 not rescale the specified values to a range of -1 to 1.

In this instrument design, the size of the pitch f-table determines the number of steps there are in an instance of the sequencer. The size of the pitch is grabbed using ftlen and is stored in ilength. If ilength is equal to 32, then the current instance of the instrument models a 32 step sequencer. Other components, such as the amplitude envelope, refer to this value.

Figure 5.9    Pitches stored in f-table 100.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ilength = ftlen(ifpch)

    aclock phasor   1/idur
    kclock downsamp aclock
    
    ktpch table  kclock * ilength, ifpch, 0, 0, 1
    kpch  =      cpspch(ktpch)
    avco1 vco    1, kpch, iwave, .5, 1, 1/(cpspch(7.00))
    avco2 vco    1, kpch*.994, iwave, .5, 1, 1/(cpspch(7.00))
    avco  =      (avco1 + avco2) * .5 * iamp
    agate linseg 0, .015625, 1, idur - .03125, 1, .015625, 0

    outs avco * agate, avco * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 

t 0 90

i1 0 4 10000 2 100
i1 + 2 10000 1 100
i1 + 2 10000 1 100
e
Figure 5.10    Orchestra and score code for east501.csd.

Envelope

The amplitude envelope component uses the second method of using table to read through an f-table. A single shape for the envelope is defined, and is applied to every note in the current instance of the sequencer. This is accomplished by multiplying the master clock with ilength and with the size of the envelope table. The ftlen opcode is used to pluck the size of the envelope, ensuring envelope tables of any size are compatible, and then this value is stored in ilenv.

atenv table aclock * ilenv * ilength , ifenv, 0, 0, 1
Figure 5.11    This table code reads through an envelope table of any size for every pitch.
Figure 5.12    A triangle-shaped envelope becomes four triangles, corresponding with a four note pitch table.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ifenv   = p7
    ilength = ftlen(ifpch)
    ilenv   = ftlen(ifenv)

    aclock phasor   1/idur
    kclock downsamp aclock

    ktpch table  kclock * ilength, ifpch, 0, 0, 1
    atenv table  aclock * ilenv * ilength , ifenv, 0, 0, 1
    kpch  =      cpspch(ktpch)
    avco1 vco    1, kpch, iwave, .5, 1, 1/(cpspch(7.00))
    avco2 vco    1, kpch*.994, iwave, .5, 1, 1/(cpspch(7.00))
    avco  =      (avco1 + avco2) * .5 * iamp * atenv
    agate linseg 0, .015625, 1, idur - .03125, 1, .015625, 0

    outs avco * agate, avco * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 
f101 0 4 -2 6.04 6.08 6.11 7.04
f200 0 8192 -7 0 4096 1 4096 0
f201 0 256 -5 .001 64 1 192 .001

t 0 90

i1 0 4 10000 2 101 200 
i1 + 2 10000 1 100 .
i1 + 2 10000 1 101 201
i1 + 1 10000 1 101 201
i1 + 1 10000 1 101 201
e
Figure 5.13    Orchestra and score code for east502.csd.

Accents and Mutes

Accent control is a common component in sequencer design for modulating dynamics of particular notes in a musical phrase. They may alter any number of properties including, but not limited to: gain, distortion, resonance, envelopes, etc...

The accent controller in this instrument only modulates the amplitude of a note. In fact, it is really just a mechanism for setting the amplitude for each note in a pattern. For this reason, it can be used as a mute controller. Amplitudes are stored using a GEN02 f-table, and can be of any value. I'm storing only three different numbers in the tables, corresponding with mute (0), normal (1), and accent (2). As an accent f-table is read through with table, the accent signal is multiplied with the audio signal, avco. The accent controller is practically identical to the way the pitch component works.

Figure 5.14    The accent table modulates the gain of individual notes of a pattern.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ifenv   = p7
    ifacc   = p8
    ilength = ftlen(ifpch)
    ilenv   = ftlen(ifenv)

    aclock phasor   1/idur
    kclock downsamp aclock

    ktpch table kclock * ilength, ifpch, 0, 0, 1
    atenv table aclock * ilenv * ilength , ifenv, 0, 0, 1
    atacc table aclock * ilength, ifacc, 0, 0, 1

    kpch  =      cpspch(ktpch)
    avco1 vco    1, kpch, iwave, .5, 1, 1/(cpspch(7.00))
    avco2 vco    1, kpch*.994, iwave, .5, 1, 1/(cpspch(7.00))
    avco  =      (avco1 + avco2) * .5 * iamp * atenv * atacc
    agate linseg 0, .015625, 1, idur - .03125, 1, .015625, 0

    outs avco * agate, avco * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 
f101 0 4 -2 6.04 6.08 6.11 7.04
f200 0 8192 -7 0 4096 1 4096 0
f201 0 256 -5 .001 64 1 192 .001
f300 0 8 -2 2 1 2 1 1 2 0 1
f301 0 4 -2 2 1 1 1
f302 0 8 -2 2 0 1 0 1 0 2 0

t 0 90

i1 0 4 10000 2 101 200 301
i1 + 2 10000 1 100 200 300
i1 + 2 10000 1 101 201 301
i1 + 2 10000 1 100 201 300
i1 + 2 10000 2 100 201 302
e
Figure 5.15    Orchestra and score code for east503.csd.

Pan

The pan component places each note in the stereo field, and works similar to the pitch and accent components. The range of the pan values fall between 0 and 1, with 0 corresponding to the left, and 1 to the right.

Figure 5.16    The pan table places notes in a stereo field.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ifenv   = p7
    ifacc   = p8
    ifpan   = p9
    ilength = ftlen(ifpch)
    ilenv   = ftlen(ifenv)

    aclock phasor   1/idur
    kclock downsamp aclock

    ktpch table kclock * ilength, ifpch, 0, 0, 1
    atenv table aclock * ilenv * ilength , ifenv, 0, 0, 1
    atacc table aclock * ilength, ifacc, 0, 0, 1
    atpan table aclock * ilength, ifpan, 0, 0, 1

    kpch  =      cpspch(ktpch)
    avco1 vco    1, kpch, iwave, .5, 1, 1/(cpspch(7.00))
    avco2 vco    1, kpch*.994, iwave, .5, 1, 1/(cpspch(7.00))
    avco  =      (avco1 + avco2) * .5 * iamp * atenv * atacc
    agate linseg 0, .015625, 1, idur - .03125, 1, .015625, 0

    outs avco * (sqrt(1-atpan)) * agate, avco * sqrt(atpan) * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 
f101 0 4 -2 6.04 6.08 6.11 7.04
f200 0 8192 -7 0 4096 1 4096 0
f201 0 256 -5 .001 64 1 192 .001
f300 0 8 -2 2 1 2 1 1 2 0 1
f301 0 4 -2 2 1 1 1
f302 0 8 -2 2 0 1 0 1 0 2 0
f400 0 8 -2 1 0 1 .25 .75 0 .5 .25

t 0 90

i1 0 4 10000 2 101 200 301 400
i1 + 2 10000 1 100 200 300 .
i1 + 2 10000 1 101 201 301 .
i1 + 2 10000 1 100 201 300 .
i1 + 2 10000 2 100 201 302 .
e
Figure 5.17    Orchestra and score code for east504.csd.

Pulse Width Modulation

The vco opcode allows for the shape of two of its three stored waveforms to be modulated. The first waveform is a static sine wave, and can not be altered. The duty-cycle of the second waveform, pulse, can be modulated with a k-rate signal. This is known as pulse width modulation (PWM). The third waveform can shift shapes between a saw, triangle and ramp. The PWM component uses a cycling envelope to modulate the waveform of each note. This works identically to the amplitude envelope component mentioned previously.

Figure 5.18    The Pulse Width Modulation envelope changes the waveshape for each note in a pattern.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ifenv   = p7
    ifacc   = p8
    ifpan   = p9
    ifpwm   = p10
    ilength = ftlen(ifpch)
    ilenv   = ftlen(ifenv)
    ilpwm   = ftlen(ifpwm)

    aclock phasor   1/idur
    kclock downsamp aclock

    ktpch table kclock * ilength, ifpch, 0, 0, 1
    atenv table aclock * ilenv * ilength , ifenv, 0, 0, 1
    atacc table aclock * ilength, ifacc, 0, 0, 1
    atpan table aclock * ilength, ifpan, 0, 0, 1
    ktpwm table kclock * ilpwm * ilength, ifpwm, 0, 0, 1

    kpch  =      cpspch(ktpch)
    avco1 vco    1, kpch, iwave, ktpwm, 1, 1/(cpspch(7.00))
    avco2 vco    1, kpch*.994, iwave, ktpwm, 1, 1/(cpspch(7.00))
    avco  =      (avco1 + avco2) * .5 * iamp * atenv * atacc
    agate linseg 0, .015625, 1, idur - .03125, 1, .015625, 0

    outs avco * (sqrt(1-atpan)) * agate, avco * sqrt(atpan) * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 
f101 0 4 -2 6.04 6.08 6.11 7.04
f200 0 8192 -7 0 4096 1 4096 0
f201 0 256 -5 .001 64 1 192 .001
f300 0 8 -2 2 1 2 1 1 2 0 1
f301 0 4 -2 2 1 1 1
f302 0 8 -2 2 0 1 0 1 0 2 0
f400 0 8 -2 1 0 1 .25 .75 0 .5 .25
f500 0 8192 -7 1 8192 0
f501 0 8192 -7 1 4096 0 4096 1

t 0 90

i1 0 4 10000 2 101 200 301 400 501
i1 + 2 10000 . 100 200 300 .   500
i1 + 4 10000 . 101 201 301 .   .
i1 + 2 10000 . 100 201 300 .   501
i1 + 2 10000 . 100 201 302 .   .
e
Figure 5.19    Orchestra and score code for east505.csd.

Voltage Controlled Filter

The use of the moogvcf is employed to evolve the timbre. Instead of applying a lowpass filter to each individual note, an envelope is applied through out the duration of the pattern, to roughly simulate the turn of a knob. Even though this f-table holds an enveloping shape rather than a small number of discrete values, the same mechanism is used as the pitch component.

Figure 5.20    An envelope shape stored in an f-table simulates the turn of a lowpass filter knob.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ifenv   = p7
    ifacc   = p8
    ifpan   = p9
    ifpwm   = p10
    ifvcf   = p11
    ilength = ftlen(ifpch)
    ilenv   = ftlen(ifenv)
    ilpwm   = ftlen(ifpwm)
    ilvcf   = ftlen(ifvcf)

    aclock phasor   1/idur
    kclock downsamp aclock

    ktpch table kclock * ilength, ifpch, 0, 0, 1
    atenv table aclock * ilenv * ilength , ifenv, 0, 0, 1
    atacc table aclock * ilength, ifacc, 0, 0, 1
    atpan table aclock * ilength, ifpan, 0, 0, 1
    ktpwm table kclock * ilpwm * ilength, ifpwm, 0, 0, 1
    atvcf table aclock * ilvcf, ifvcf, 0, 0, 1

    kpch  =       cpspch(ktpch)
    avco1 vco     1, kpch, iwave, ktpwm, 1, 1/(cpspch(7.00))
    avco2 vco     1, kpch*.994, iwave, ktpwm, 1, 1/(cpspch(7.00))
    avco  =       (avco1 + avco2) * .5
    avcf  moogvcf avco, atvcf, 0
    avcf  =       avcf * iamp * atenv * atacc
    agate linseg  0, .015625, 1, idur - .03125, 1, .015625, 0

    outs avcf * (sqrt(1-atpan)) * agate, avcf * sqrt(atpan) * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 
f101 0 4 -2 6.04 6.08 6.11 7.04
f200 0 8192 -7 0 4096 1 4096 0
f201 0 256 -5 .001 64 1 192 .001
f300 0 8 -2 2 1 2 1 1 2 0 1
f301 0 4 -2 2 1 1 1
f302 0 8 -2 2 0 1 0 1 0 2 0
f400 0 8 -2 1 0 1 .25 .75 0 .5 .25
f500 0 8192 -7 1 8192 0
f501 0 8192 -7 1 4096 0 4096 1
f600 0 8192 -7 500 8192 6000
f601 0 8 -2 6000 500 1000 200 4000 2000 1000 500

t 0 90

i1 0 4 10000 1 101 200 301 400 501 600
i1 + 2 10000 . 100 200 300 .   500 .
i1 + 4 10000 . 101 201 301 .   .   .
i1 + 4 10000 . 100 201 300 .   501 601
i1 + 4 10000 . 100 201 302 .   .   .
e
Figure 5.21    Orchestra and score code for east506.csd.

Resonance

The final component of this instrument is resonance. Just like the lowpass, the resonance component controls a parameter of the moogvcf. The mechanism for modulating the resonance during an instance of a pattern is also identical to that of the lowpass. The workable range of the moogvcf resonance parameter is between 0 and 1. As the value approaches 1, the filter begins to self-oscillate, adding a TB-303 like quality to the sound.

Figure 5.22    An envelope shape stored in an f-table simulates the turn of a resonance knob.
instr 1
    idur    = p3
    iamp    = p4
    iwave   = p5
    ifpch   = p6
    ifenv   = p7
    ifacc   = p8
    ifpan   = p9
    ifpwm   = p10
    ifvcf   = p11
    ifres   = p12
    ilength = ftlen(ifpch)
    ilenv   = ftlen(ifenv)
    ilpwm   = ftlen(ifpwm)
    ilvcf   = ftlen(ifvcf)
    ilres   = ftlen(ifres)

    aclock phasor   1/idur
    kclock downsamp aclock

    ktpch table kclock * ilength, ifpch, 0, 0, 1
    atenv table aclock * ilenv * ilength , ifenv, 0, 0, 1
    atacc table aclock * ilength, ifacc, 0, 0, 1
    atpan table aclock * ilength, ifpan, 0, 0, 1
    ktpwm table kclock * ilpwm * ilength, ifpwm, 0, 0, 1
    atvcf table aclock * ilvcf, ifvcf, 0, 0, 1
    atres table aclock * ilres, ifres, 0, 0, 1

    kpch  =       cpspch(ktpch)
    avco1 vco     1, kpch, iwave, ktpwm, 1, 1/(cpspch(7.00))
    avco2 vco     1, kpch*.994, iwave, ktpwm, 1, 1/(cpspch(7.00))
    avco  =       (avco1 + avco2) * .5
    avcf  moogvcf avco, atvcf, atres
    avcf  =       avcf * iamp * atenv * atacc
    agate linseg  0, .0625, 1, idur - .125, 1, .0625, 0

    outs avcf * (sqrt(1-atpan)) * agate, avcf * sqrt(atpan) * agate
endin
f1 0 8192 10 1
f100 0 8 -2 7.00 7.03 7.02 7.06 7.05 7.09 7.08 7.12 
f101 0 4 -2 6.04 6.08 6.11 7.04
f200 0 8192 -7 0 4096 1 4096 0
f201 0 256 -5 .001 64 1 192 .001
f300 0 8 -2 2 1 2 1 1 2 0 1
f301 0 4 -2 2 1 1 1
f302 0 8 -2 2 0 1 0 1 0 2 0
f400 0 8 -2 1 0 1 .25 .75 0 .5 .25
f500 0 8192 -7 1 8192 0
f501 0 8192 -7 1 4096 0 4096 1
f600 0 8192 -7 500 8192 6000
f601 0 8 -2 6000 500 1000 200 4000 2000 1000 500
f700 0 8 -2 .5 .2 .7 .1 .4 .2 .6 .8
f701 0 8192 -7 .9 8192 0
f702 0 8192 -5 .8 4096 .2 4096 .8

t 0 90

i1 0 4 10000 1 101 200 301 400 501 600 701
i1 + 2 10000 . 100 200 300 .   500 .   700
i1 + 4 10000 . 101 201 301 .   .   .   701
i1 + 4 10000 . 100 201 300 .   501 601 702
i1 + 4 10000 . 100 201 302 .   .   .   .
e
Figure 5.23    Orchestra and score code. east507.csd