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