by Anton Kholomiov
Quick start types and functions.
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
> 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
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):
> 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
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
fades :: D -> D -> Sig fades fadeInTime fadeOutTime = ...
> 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
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
blp, bhp :: Sig -> Sig -> Sig blp centerFreq ain = aout bbp, bbr :: Sig -> Sig -> Sig -> Sig bbp centerFreq reson ain = aout
> 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
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
skipNorm :: Tab -> Tab
> dac $ mul (uosc 0.5 * usqr 4) $ oscBy (sines [1, 0.5, 0, 0, 0.25]) 220
Creates audio signal out, instrument definition and user midi input.
midi :: Sigs a => (Msg -> SE a) -> SE a
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
> 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: 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
type MaxDelayTime = D type Feedback = Sig type Balance = Sig echo :: MaxDelayTime -> Feedback -> Sig -> SE Sig pingPong :: DelayTime -> Feedback -> Balance -> Sig2 -> SE Sig2
> 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
There are certain magic functions that are defined using arguments of many types.
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
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
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) = ...