In CLM, instruments are defined by using the macro DEFINSTRUMENT, a template for which is
as follows.
The contents of brackets "< > " indicate sections to be defined by the user.
Comments are preceded with a semi-colon, ";".
(definstrument <name> (<arg1 arg2 arg3...>) ; a name, followed by a list of args to ; be called by the user ; initialization variables and values (let ((< init-variable1 value1> ) (< init-variable2 value2> ) (< init-variable3 value3> )) ; the RUN loop computes each sample, one at a time (Run (loop for i from < first sample> to < last sample> do ; the "body" defines exactly how each sample is to be computed < body> )))) |
This example demonstrates the RUN loop; the variables (commonly named) BEG and END; and the functions MAKE-OSCIL and OSCIL.
(definstrument examp1 (start-time duration frequency amplitude) (let* ((beg (floor (* start-time sampling-rate))) (end (+ beg (floor (* duration sampling-rate)))) (sine-wave (make-oscil :frequency frequency))) (Run ; Run ensures that the DSP (not RAM) will be used ; to compile the samples -- it's much faster! (loop for i from beg to end do (outa i (* amplitude (oscil sine-wave))))))) |
Once defined, a CLM instrument must be compiled before it will run. This is done by converting the file which contains the code into binary (where it acquires the ".fasl" extension). There are several ways to do this, but perhaps the simplest way is to use LISP's top-level command, :cl, for compile and load. This command takes one argument, the file name to be compiled and loaded, so for example, to compile the file, "test.lisp" which is in your home directory, enter the following.
:cl ~/test.lisp
Remember that every time you modify your instrument, you will have to first save the file, and then re- compile and load, as described above.
To call the instrument defined above, use the function WITH-SOUND.
With these arguments, it will sound a bit like a tuning fork.
(with-sound () (examp1 0 1 440.0 0.75)) | | | | | corresponding with: name start dur freq amp
The empty parens following with-sound are where certain global values can be specified (such as the number of channels, sampling rate, name of resulting soundfile etc.), all of which have defaults. More on this later.
WITH-SOUND automatically plays the sound once it is computed. This is done by (invisibly) invoking the DAC function. To hear the sound again, you will need to do this explicitly. Just type:
(dac)
DAC, like this, with no arguments plays the last soundfile computed. To hear some other soundfile, you will need to give the pathname as an argument. Eg.
(dac "/zap/my-sound.snd")
(definstrument examp2 (start-time duration frequency amplitude &optional (amp-env '(0 0 50 1 100 0))) (let* ((beg (floor (* start-time sampling-rate))) (end (+ beg (floor (* duration sampling-rate)))) (sine-wave (make-oscil :frequency frequency)) (amp (make-env :envelope amp-env :scaler amplitude :start-time start-time :duration duration))) (Run (loop for i from beg to end do (outa i (* (env amp) (oscil sine-wave))))))) |
This is basically the same as example 1. A new argument have been added to the argument list: amp-env. This has been made an optional argument, and if not specified when the instrument is called, it will take as a default value the shape given by the list (0 0 50 1 100 0) -- a simple triangle. Observe the syntax carefully.
A new initialization variable, amp is also used. This creates an envelope structure with MAKE-ENV, using the values of amplitude (as a scaler) and amp-env (as an envelope shape). Having set up the envelope in this way, it is called in the Run loop with ENV, and it's value multiplied by the value from sine-wave, to produce a sample value. In CLM, structures that are set up using MAKE-* (where * is the name of a structure) are called in the RUN loop with the name of the structure itself. Eg.
Initialization | Run loop | Structure |
MAKE-OSCIL | OSCIL | a sine wave oscillator |
MAKE-ENV | ENV | an envelope function |
MAKE-DELAY | DELAY | a delay line |
CLM carries out a certain amount of error-checking for you, and reports back with error messages if something seems wrong. A lot of the time, errors are caused by typos: for example, supposing in example 2, the envelope variable amp is misspelled as "imp", this would produce the following errors.
Warning: Symbol AMP declared special
Warning: variable IMP is never used
In general, something is "declared special" if it is called in the Run loop without having been defined elsewhere. A warning is also given if a variable defined in the let, is not called in the Run loop. These are handy, and make debugging a lot easier.