CSOUND JOURNAL ISSUE 23

INDEX | ABOUT | LINKS

Csound-expression Reference

by Anton Kholomiov

Quick start types and functions.

Basic types

In the library we have several basic types:

Sig  -- audio and control signals

D    -- constant numbers

Tab  -- functional tables

SE   -- Side-effects

Spec -- spectrums (used in pvs opcodes)

Rendering the audio

dac   -- send audio to speakers

dacBy -- supply options (rates, drivers, midi-devices)

vdac  -- dac with virtual midi-keyboard.

writeSnd -- render audio to file offline

writeSndBy -- supply options (rates, drivers, midi-devices)

setRates  -- sets the sample rate and the block size

setJack   -- sets the jack name

Examples:

> let opt = setRates 48000 128 <> setJack "sine-wave"
> dacBy opt (osc 220)

We use the operator <> to combine options. See the standard class Data.Monoid for more information.

Sound design tools

Audio waves

Pure sine, sawtooth, square, triangle, and pulse with adjustable width:

osc, saw, sqr, tri :: Sig -> Sig

pw :: Sig -> Sig -> Sig
pw bandwidth frequency = ...

Unipolar waves (useful for LFOs): uosc, usaw, usqr, utri.

Examples:

> dac $ mul 0.5 $ tri $ 220 * (1 + 0.08 * uosc 3)

> dac $ mul 0.25 $ pw (0.5 * uosc 0.12) 220 + pw (0.2 + 0.3 * uosc 0.2) 220

Envelope generators

linseg, expseg :: [D] -> Sig

Just like in Csound but arguments are passed in the list, and the last value is held:

> linseg [0, 0.2, 1, 1.3, 0.5, 1.5, 0]

So the zero is held. It is not going to drop down to infinity.

[[[Actually linseg in Csound already behaves in the same way so this point is perhaps inaccurate. line and expseg will however continue along their defined trajectory.]]]

Linear adsr and exponential adsr envelope generators:

leg, xeg :: D -> D -> D -> D -> Sig

Attack-sustain-release envelope:

fades :: D -> D -> Sig
fades fadeInTime fadeOutTime = ...

Examples:

> dac $ osc $ 220 * (1 + 0.5 * linseg [0, 2, 1, 2, 0.5, 1, 0.5, 1, 0])

> let env = leg 0.02 0.1 0 0

> dac $ mul env $ sqr $ 220 * env

> vdac $ midi $ onMsg $ mul (fades 0.1 0.5) . osc

Filters

Moog-like low-pass filter:

mlp :: Sig -> Sig -> Sig -> Sig
mlp centerFreq resonance asig = aout

Note that the order of arguments is reversed with respect to the ordering used in Csound. The reason for this is that in Haskell it is convenient to use less arguments as first arguments. Because in Haskell we have partial application. With partial application if we apply a single argument to the function of to arguments, it will not lead to a type error. It creates a function of one argument. The first argument is bound to a passed value and the second, therefore, is free to be used.

Here is an example:

> :t lp
mlp :: Sig -> Sig -> Sig -> Sig

> :t (lp 1500)
(mlp 1500) :: Sig -> Sig -> Sig

> :t (mlp 1500 0.4)
(mlp 1500 0.4) :: Sig -> Sig

> :t (mlp 1500 0.4 $ saw 200)
(mlp 1500 0.4 $ saw 200) :: Sig

We gradually reduce the number of arguments in the expression by passing more arguments to the function mlp. The order of arguments is the same for other filters.

Ordinary filters, low, high, band pass and band reject filters:

lp, hp, bp, br :: Sig -> Sig -> Sig -> Sig

Add the prefix z for zero-delay filters:

zlp, zhp, zbp, zbr :: Sig -> Sig -> Sig -> Sig

Ladder filters (moog-like and zero delay):

ladder, zladder :: Sig -> Sig -> Sig -> Sig

Butterworth filters:

blp, bhp :: Sig -> Sig -> Sig

blp centerFreq ain = aout

bbp, bbr :: Sig -> Sig -> Sig -> Sig

bbp centerFreq reson ain = aout

Examples:

> dac $ mlp (3500 * uosc 1) 0.1 $ saw 220

> dac $ mlp (3500 * uosc (linseg [1, 2, 4, 1, 2, 0.5, 8, 0.5, 2, 4, 0])) 0.1 $ saw 220

Creation of functional tables

Play oscillator with given table:

oscBy :: Tab -> Sig -> Sig

Harmonic series

sines :: [Double] -> Tab

Harmonic series with exact frequencies:

type PartialNumber = Double
type PartialStrength = Double

sines2 :: [(PartialNumber, PartialStrength)] -> Tab

Linear and exponential curves:

lins, exps :: [Double] -> Tab

Set the table size and add a guard point:

setSize :: Int -> Tab -> Tab
guardPoint :: Tab -> Tab

Skip normalization:

skipNorm :: Tab -> Tab

Examples

> dac $ mul (uosc 0.5 * usqr 4) $ oscBy (sines [1, 0.5, 0, 0, 0.25]) 220

Midi

Creates audio signal out, instrument definition and user midi input.

midi :: Sigs a => (Msg -> SE a) -> SE a

The Msg is the midi message. We can read amplitude and frequency with ampCps function:

ampCps :: Msg -> (D, D)

The useful function onMsg converts a function that takes a frequency signal, a constant, or a pair of amplitude and frequency components to the function that is defined on messages. It often goes hand-in-hand with the function midi:

> vdac $ midi $ onMsg osc

We can add envelopes to remove clicks and pops:

> let synt cps = mul (fades 0.01 0.5) $ osc cps

> vdac $ mul 0.5 $ midi $ onMsg synt

Reverbs

Reverbs: smallRoom2, smallHall2, largeHall2, magicCave2:

> let x = mul (uosc 0.5 * usqr 4) $ oscBy (sines [1, 0.5, 0, 0, 0.25]) 220

> dac $ mixAt 0.25 largeHall2 x

> let synt = midi $ onMsg $ mul (fades 0.01 0.7) . tri

> vdac $ mul 0.25 $ mixAt 0.25 magicCave2 synt

Delays

type MaxDelayTime = D
type Feedback = Sig
type Balance = Sig

echo :: MaxDelayTime -> Feedback -> Sig -> SE Sig
pingPong :: DelayTime -> Feedback -> Balance -> Sig2 -> SE Sig2

Example:

> let synt = midi $ onMsg $ mul (fades 0.01 0.7) . tri

> vdac $ mul 0.25 $ mixAt 0.25 largeHall2 $ mixAt 0.65 (echo 0.5 0.8) synt

Magic functions

There are certain magic functions that are defined using arguments of many types.

Volume control

We can scale the amplitude of something that produces signals. It can be a single signal or tuples of signals or signals wrapped in the SE, or produced with a UI-widget.

mul :: Audible a => Sig -> a -> a

Transformation of signals

The at function converts something audible with a signal-like function, and mixAt converts using a dry-wet ratio. It is the first argument that ranges from 0 (all dry) to 1 (all wet).

at    :: Audible a => (Sig -> Sig) -> a -> a

mixAt :: Audible a => Sig -> (Sig -> Sig) -> a -> a

Patches

Play a patch with midi:

atMidi :: Sigs a => Patch a -> SE a

Play a single note:

atNote :: Sigs a => Patch a -> (D, D) -> SE a
atNote patch (amplitude, frequency) = ...