Let's install everything. First thing we need is a csound compiler. When it's installed properly we can type in the terminal:
It will print the long message. Ubunut/Dbian users can install the Csound with
> sudo apt-get install csound csound-gui
Next thing is a working Haskell environment with
It can be installed with Haskell Platform.
If it works to install the
csound-expression we can type in the terminal:
> cabal install csound-expression
Let's have a break and take a cup of tea. The library contains a lot of modules to install.
The first sound
Let's start the
ghci and load the main module
Csound.Base. It exports
> ghci Prelude> :m +Csound.Base Prelude Csound.Base>
We can play a sine wave with 440 Hz:
> dac $ osc 440
Pressing Ctrl+C stops the sound. The expression
osc 440 makes the sine wave and
dac makes a file
tmp.csd in the current directory invokes the
on it and sends the output to speakers.
Let's modify the signal. It has a constant amplitude envelope but we can change it
> dac $ linseg [0, 1, 1, 3, 0] * osc 440
linseg creates a signal of linear segments
linseg [a, durA, b, durB, c, durC, ...]
It starts from the
a value then in
durA seconds it goes to the value
durB seconds it goes to the value
c etc. It stays constant at the
final value. So the expression
linseg [0, 1, 1, 3, 0] produces an linear envelope
that starts from
0 then reaches
1 (maximum for the sound signal) in one second
and finally goes to zero in 3 seconds.
Let's add a 5% vibrato to the sound:
> dac $ linseg [0, 1, 1, 3, 0] * osc (440 * (1 + 0.05 * osc 5))
We can factor out the envelope and apply it to the amplitude and the vibrato:
> let env = linseg [0, 1, 1, 3, 0] > dac $ env * osc (440 * (1 + 0.05 * env * osc 5))
We can apply an envelope to any parameter. Let's make a slide, and change the waveform to sawtooth:
> dac $ env * saw (220 + 220 * env)
We can play a tone with three odd harmonics:
> dac $ env * oscBy (sines [1, 0, 0.5, 0, 0.25]) 440
We can play an mp3 file:
> dac $ ar2 $ mp3in $ text "/home/anton/listen/The Kinks-Waterloo Sunset.mp3"
We used the functions:
ar2 :: (Sig, Sig) -> (Sig, Sig) text :: String -> Str mp3in :: Tuple a => Str -> a
mp3in takes a Csound-string with the filename and produces a sound of the mp3 file.
ar2 forces the output to be a pair of signals. The function
converts the Haskell strings to Csound strings.
There is even better way to read sound files. What if we have a short drum loop and we want to mix it with harmony. We need only 10 minutes of it. We can do it like this:
> dac $ takeSnd (10 * 60) $ loopSnd "drum.wav" + (mul 0.5 $ loopSnd "harmony.mp3")
Let's review the functions:
loopSnd :: String -> (Sig, Sig) takeSnd :: Sigs a => Double -> a -> a mul :: SigSpace a => Sig -> a -> a
loopSnd repeats endlessly a given file (note that it's important
to write the files with extensions. Wav-files and mp3s a treated differently
the decision is based on the file extension).
mul scales the tuples of signals with a scalar signal.
takeSnd truncates the sound to the given amount of seconds.
Let's take a look at the types of the functions:
osc :: Sig -> Sig saw :: Sig -> Sig oscBy :: Tab -> Sig -> Sig linseg :: [D] -> Sig text :: String -> Str mp3in :: Tuple a -> Str -> a
These functions almost all primitive types of the library. Let's look at the types:
Sig: a numeric signal (a stream of numbers)
D: a number
Str: a string
Tab: an array of numbers (See the module
Csound.Tabfor constructors of the arrays)
Spec: a spectrum of the signal
Tuple: type class of tuples of csound values
Arg: type class of scalar csound values (they are not signals or spectrums)
Sigs: type class of tuples of signals.
Unit: the csound tuple of zero length. It's constructed with the function
Functions that produce a random values or procedures are wrapped
in the special type
SE (or side effect). Let's look at the white noise
noise :: Sig -> Sig -> SE Sig
It takes an amplitude and the beta of low pass filter and returns a signal that
is wrapped in the
SE. The type
SE is a
so we can use the standard functions to process it:
> dac $ fmap (env * ) $ noise 1 0
We can make a sound in real time with midi keyboard. Midi instrument has the type:
instr :: Sigs a => Msg -> SE a
Msg signifies the midi-messages, the type class
contains all tuples of signals. Let's make a simple midi instrument:
> let instr msg = return $ 0.5 * sig (ampmidi msg 1) * fades 0.01 0.5 * osc (sig $ cpsmidi msg)
fades function adds fade in and fade out phases to the signal with
the specified given length. With the functions
we can read the amplitude and frequency from the midi message.
sig converts numbers to signals.
Now we can play it with the function
> dac $ midi instr
If we don't have a hardware midi-device we can use a virtual midi-keyboard.
To do it we need to use
vdac in place of
> vdac $ midi instr
midi takes a mid-instrument and starts to listen
on all channels for the events. If we want to specify the concrete channel
we should use the function
midin :: Sigs a => Int -> (Msg -> SE a) -> SE a
What if we want to play a pure tone on the first channel and the sawtooth on the second one? We can just add the resulting signals. First let's take a waveform as a parameter for the instrument:
> let instr f msg = return $ 0.5 * sig (ampmidi msg 1) * fades 0.01 0.5 * f (sig $ cpsmidi msg)
Now let's set up everything and add some reverb:
> vdac $ fmap (\asig -> 0.5 * nreverb asig 1 0.2) $ (midin 1 $ instr osc) + (midin 2 $ instr saw)
The example shows that applying the effect is as simple as applying a function.
But notice the use of
fmap. We are applying the function to the signal
that is wrapped in the SE. That's why we should use
We can use a shortcut in defining the midi-instrument. There is a class that converts expressions to midi-instruments:
class MidiInstr a where type MidiInstrOut a :: * onMsg :: a -> Msg -> SE (MidiInstrOut a)
The only one method
onMsg takes something and converts it to midi-instruments.
We can define an instrument from the wave form:
> vdac $ midi $ onMsg osc
Here the method takes a function that converts a frequency to signal. The method gets the frequency from the midi message, converts it to a constant signal, applies the given waveform to it and multiplies everything on the amount of the amplitude taken from the midi message.
The type class
MidiInstr contains many useful instances. You can convert
anything that expects an amplitude and frequency and produces the tuple of signals:
(D, D) -> Sig (D, D) -> SE Sig (D, D) -> (Sig, Sig) (D, D) -> SE (Sig, Sig) ...
We can trigger an instrument with the event streams. Event stream contains some primitive scalar csound-values (they are not signals).
The most simple event stream is created with the function
metroE :: Sig -> Evt Unit
It creates a stream of repeating events. the first argument is the frequency of the repetition (in Hz). Let's create a stream of notes:
> let notes = fmap (const 440) $ metroE 2
Now we can trigger the instrument on the stream with the function
sched :: (Arg a, Sigs b) => (a -> SE b) -> Evt (D, a) -> b
It takes an instrument, the event stream and produces the mixed signal.
An instrument is a function it takes a tuple of primitive Csound values
Arg) and produces a tuple of signals (type class
An event stream contains a list of pairs. It's duration of the note and
the parameter for the instrument.
Let's create a simple instrument:
> let instr x = return $ 0.5 * osc (sig x)
And trigger the notes:
> dac $ sched instr $ withDur 0.25 notes <interactive>:63:34: Couldn't match type `Integer' with `D' Expected type: Evt D Actual type: Evt Integer In the second argument of `withDur', namely `notes' In the second argument of `($)', namely `withDur 0.25 notes' In the second argument of `($)', namely `sched instr $ withDur 0.25 notes'
We've got an error message about using
Integer in place of
Let's look at the type of the
> :t notes notes :: Evt Integer
ghci converts numeric literals to integers, but we need a csound integer.
Let's give the
ghci a hint:
> let notes = fmap (const (440::D)) $ metroE 2 > :t notes notes :: Evt D
Now we can invoke the instrument and hear the result:
> dac $ sched instr $ withDur 0.25 notes
withDur appends a constant value for the duration of the note
to all events on the stream. Let's make out events more interesting.
We can play a list of events in the loop and make it faster:
> let notes = cycleE [440::D, 330, 220] $ metroE 4
Or we can play them at random and skip some elements with the inverse of the given frequency:
> let notes = randSkip 0.75 $ oneOf [440::D, 330, 220] $ metroE 4
Event streams are monoids. We can merge two event streams together with
mappend. Let's merge two streams. One plays a note 440 every 3
beat and another plays a 330 every 7 beat.
> let notes = let m = metroE 4 in (mappend (every 0  $ repeatE (440 :: D) m) (every 0  $ repeatE 330 m))
We used the functions:
repeatE :: a -> Evt b -> Evt a -- repeats the same event every :: Int -> [Int] -> Evt a -> Evt a -- skips events from the stream -- in beat patterns every firstSkip beatPattern evt
Beat pattern is a sequence of integers. Every integer
in the sequence means play one beat and skip
There are many functions defined for events. We can find them
in the module
We can play a list of notes.
> let notes = CsdEventList 4 [(0, 1, 440::D), (1, 1, 220), (2, 2, 330)]
CsdEventList contains the total duration of the scores
and the list of triplets:
(startTime, duration, instrumentArguments).
Now we can trigger the instrument:
> dac $ mix $ sco instr notes
sco :: (Arg a, Sigs b, CsdSco f) => (a -> SE b) -> f a -> f (Mix b) mix :: (Sigs a, CsdSco f) => f (Mix a) -> a
sco takes an instrument and a list of notes and
converts it to the list of sounds. The function
the list of sounds to the single sound. But what is the type class
It's a generic type of the values that can be converted to the
We can find out the complete definition in the module
csound-expression is meant to be open to any score-generation libraries.
To use our favorite library we should make in instance for the type class
There is an instance for the type
Score from the library
It's in the separate package
temporal-csound. We can install it with cabal-install:
> cabal install temporal-csound
Now we can load the csound with module
> ghci Prelude> :m +Csound
It reexports the module
Csound.Base and brings the definition of the instance
CsdSco in the scope. There are seven main functions to remember:
-- Constructs a score with the single note (it lasts for one second) temp :: a -> Score a -- Constructs a score that contains nothing and lasts for some time. rest :: Double -> Score a -- Stretches the score in the time domain with the given coefficient. -- It gets faster or slower. str :: Double -> Score a -> Score a -- Delays all events with the given amount of time. del :: Double -> Score a -> Score a -- A sequential composition of scores. It's short for melody. -- It plays the scores one after the other. mel :: [Score a] -> Score a -- A parallel composition. It's short for harmony. -- It plays all scores at the same time. har :: [Score a] -> Score a -- Repeats the score several times. loop :: Int -> Score a -> Score a
Let's make a simple tune:
Prelude Csound> let ns = fmap temp [220, 330, 440::D] Prelude Csound> let notes = str 0.25 $ mel [loop 2 (mel ns), har ns] Prelude Csound> dac $ mix $ sco instr notes
We can hear the distortion in the final chord. A sound signal is a function that can not exceed the value of 1. It's clipped before it's send to the speakers. We can scale the sound to remove the distortion:
> dac $ 0.5 * (mix $ sco instr notes)
What if we don't want or tune to fade out? We can apply an effect to it. First let's define a fader instrument. It takes a signal and multiplies it with an linear envelope:
> let fader x = return $ linseg [1, idur * 0.5, 1, idur * 0.5, 0] * x
idur is the hack that let's us query the total duration of the note.
If you know the Csound it's equivalent to the argument
p3 of the instrument.
So the fader let's the signal to be unchanged for the first half of the note
and then reduces it to zero in the second one.
Now we can use the
eff :: (Sigs a, Sigs b, CsdSco f) => (a -> SE b) -> f (Mix a) -> f (Mix b)
It applies the effect to the unmixed list of sounds:
> dac $ mix $ eff fader $ sco instr notes
Sometimes we can render the Csound file to sound files much faster then real-time rendering. It's more convenient to listen to the result in the media player. we can take a closer look at some details.
We can render the file without playing (the function
specify the wav-file as the output in the options.
> csdBy (setOutput "tmp.wav") $ mix $ eff fader $ sco instr notes
It saves the file to the
tmp.csd with output set to
and renders it with
csound then creates a wav-file.
And now we can listen to it in the player:
> :!mplayer tmp.wav
There are shortcut functions for Linux-users:
They save the output to file and invoke the given media-player on it.
> mplayer $ mix $ eff fader $ sco instr notes
Instruments can return a tuple of signals or a single signal or
a tuple signals wrapped in the type
SE. There are many different
variants of the output. But often we want to process the output as
a single signal. The output is always a container of signals.
To simplify this task there is a class
class SigSpace a where mapSig :: (Sig -> Sig) -> a -> a bindSig :: (Sig -> SE Sig) -> a -> SE a
With method from this class we can easily apply the effects to the different types of the output. There is a very often used special case:
mul :: SigSpace a => Sig -> a -> a mul k = mapSig ( * k)
It scales the output.
Sometimes our instruments are pure functions. But all functions
that invoke instruments require them to return a result that is wrapped
in the type
SE. Often we can lift the instrument on the fly
with methods from the special classes:
class Outs a where type SigOuts a :: * toOuts :: a -> SE (SigOuts a) onArg :: Outs b => (a -> b) -> (a -> SE (SigOuts b)) class CpsInstr a where type CpsInstrOut a :: * onCps :: a -> (D, D) -> SE (CpsInstrOut a) class AmpInstr a where type AmpInstrOut a :: * onamp :: a -> D -> SE (AmpInstrOut a)
onArg unifies the pure and non pure instruments.
We can use it like this:
> let notes = temp (440 :: D) > let instr cps = osc $ sig cps > dac $ mix $ sco (onArg instr) notes
Now we don't need to wrap the output in the type
SE with the method
onCps defines the instruments that take an amplitude
and frequency as input. For example, we can convert to this type of instrument
> let notes = temp (0.5 :: D, 440 :: D) > dac $ mix $ sco (onCps osc) notes
onAmp defines the instruments that take only an amplitude.
They are drum sounds or noises. It can construct instruments from constants
by scaling the sound with the input amplitude.
> let notes = str 0.25 $ loop 4 $ mel [temp (0.5::D), rest 1] > let instr = noise 1 0 > dac $ mix $ sco (onAmp instr) notes
Catalog of the instruments
We can find many predefined instruments in the package
Let's install it:
> cabal install csound-catalog
Let's try it in the interpreter:
> ghci Prelude> :m +Csound Prelude> :m +Csound.Catalog Prelude Csound Csound.Catalog> vdac $ mul 0.3 $ midi $ onMsg (mul (fades 0.01 3) . vibraphone1)
> vdac $ mul 0.5 $ midi $ onMsg (mul (fades 0.01 3) . delayedString)
> vdac $ mul 0.15 $ fmap largeHall $ midi $ onMsg (mul (fades 1 2) . stringPad 1)
With Gui elements (
Csound.control.Gui) we can update
the synth paramters online. There are sliders, knobs, numeric fields, rollers.
The Gui element contains three parts:
SE (Gui, Input a, Output a) Gui -- visual representation of the element Input a = a -- reads the current value Output a = a -> SE () -- writes the value to the element
Some elements can only read values (no output), some of the can only show the values (no input), some of them are static elements (no inputs and outputs).
Let's create a simple pure tone sound and update volume and frequency with sliders:
import Csound.Base main = dac $ do (gVol, vol) <- slider "volume" (linSpan 0 1) 0.5 (gCps, cps) <- slider "frequency" (expSpan 100 1000) 440 panel $ ver [gVol, gCps] return $ vol * osc cps
The function slider expects a label, value diapason and initial value. It returns a pair of visual representation and the current value:
type Source a = SE (Gui, Input a) slider :: String -> ValSpan -> Double -> Source a
First, we create to sliders for volume and frequency:
(gVol, vol) <- slider "volume" (linSpan 0 1) 0.5 (gCps, cps) <- slider "frequency" (expSpan 100 1000) 440
The first has linear diapason from 0 to 1 (linSpan) and the second has exponential diapason from (expSpan). Then we place our GUI elements on the screen. we create a panel that contains the elements with vertical placement:
panel $ ver [gVol, gCps]
The sliders are automatically aligned. We can find the other layout
function in the module
can modify the appearance of the elements with functions
from the module
At the end we return the signal that depends on the values of the GUI elements:
return $ vol * osc cps
We can listen for the keyboard events with functions:
data KeyEvt = Press Key | Release key data Key = CharKey Char | F1 | F2 | ... | LeftShift | RightShift | ... keyIn :: KeyEvt -> Evt Unit charOn :: Char -> Evt Unit charOff :: Char -> Evt Unit
charOff are handy shortcuts for
charOn = keyIn . Press . CharKey charOff = keyIn . Release . CharKey
For example, we can trigger the note with key 'a':
instr _ = return $ mul (fades 0.5 1) $ osc 440 res = schedUntil instr (charOn 'a') (charOff 'a') main = dac res
schedUntil function triggers the instrument with
the first event stream and holds the notes while
the second event stream is silent.