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.

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
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
isize1 = ftlen(1) isize2 = ftlen(2)
atable table aphasor * 8192, 1
itable = 1 isize = ftlen(itable) atable table aphasor * isize, itable
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:- f refers to an f-table
- l refers to the length of an f-table
- t refers to the signal generated from the table opcode.
ifenv = ...
ilenv = ...
atenv table ...
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
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.

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
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

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
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.

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
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.

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
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.

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
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.

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
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.

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