Music 220B

Running CLM in Real-time (part 1)

Getting Started

CLM instruments can be run in real-time if defined as parallel instruments. A parallel instrument is one that can evaluate its samples in parallel instead of sequentially (as any normal CLM instrument), in other words parallel instruments are polyphonic, real-time instruments, they can play more than one note at a time and send them directly to the computer's DAC.

To define a parallel instrument we use the defpinstrument macro (instead of definstrument). The style of the Lisp code defining the instrument is pretty much the same as for normal CLM instruments:

;;; simp-pins-0.cl
;;; A simple CLM parallel instrument

(defpinstrument simp-pins-0 (start dur frq amp)
  (let* ((sine-wave (make-oscil :frequency frq)))
    (multiple-value-bind (beg end)
	(get-beg-end start dur)
    ;;; Run loop
    (run
     (loop for i from beg to end do
	   (outa i (* amp (oscil sine-wave))))))))
After compiling and loading this instrument you can call it using the with-psound macro. The instrument will play in real-time, sending its output to the DAC (instead of creating a soundfile in /zap/test.snd and then playing it as normal CLM instruments do):
(with-psound ()(simp-pins-0 0.0 2.0 260.0 0.1))
You can call the instrument in parallel to play chords:
(with-psound ()
	     (simp-pins-0 0.0 2.0 (pitch 'c4) 0.1)
	     (simp-pins-0 0.0 2.0 (pitch 'e4) 0.1)
	     (simp-pins-0 0.0 2.0 (pitch 'g4) 0.1))

Real-time controls

Although this is nice, a better way to use a real-time instrument would be to control its parameters also in real-time. For example, in the previous instrument the frequency of the oscillator as well as the amplitude of the output are good candidate parameters to control. We do this using envelopes in normal CLM instruments but in that case the shape of our control if fixed and we cannot alter it. We need a way to communicate with the instrument while it is playing and change its parameters on the fly.

CLM provides a set of real-time controllers for parallel instruments. These controllers have the appearance of graphic widgets that can be assembled into a control panel on the screen. Let define amplitude and frequency controllers for the previous instrument:


;;; simp-pins-1.cl
;;; A simple CLM parallel instrument
;;; with real-time controls

;;; global variables for controllers
(defparameter simp-on 1000)
(defparameter simp-amp 1001)
(defparameter simp-frq 1002)

(defpinstrument simp-pins-1 ()
  (let* ((sine-wave (make-oscil :frequency 440.0))
	 (ampf (make-fcontrol simp-amp))   ;;; create amplitude control
	 (frqf (make-fcontrol simp-frq)))  ;;; create frequency control
    ;;; Run loop
    (run
     ;;; let loop run forever...
     (loop for i from 0 do
	   ;;; ...and set a controller to make it finish
	   (when (= (control simp-on) 0.0)(loop-finish))
	   ;;; set the frequency of the oscillator
	   ;;; to the value read from the associated controller
	   (setf (frequency sine-wave)(fcontrol frqf))
	     ;;; send signal to the output
             ;;; scaled by the amplitude controller value 
	     (outa i (* (fcontrol ampf)(oscil sine-wave)))))))
As you can see we first define global variables for our controls. The integers to which they are bound represent the memory position of the associated controller, more about this later. The instrument definition has some changes. We don't need to pass any parameters to the instrument (its parameter list is empty); in the initialization section the sine-wave oscillator is created with a default frequency of 440.0, but this value will be overriden by the frequency controller value in run-time; two new variables are created to hold the controls: ampf (for the amplitude control) and frqf (for the frequency control). The function make-fcontrol creates a new control object poiting to the memory position passed by its argument, in this case this pointers were stored in the global variables simp-amp and simp-frq defined at the begining of the code.

The run loop is defined in a different way. Instead of giving a starting time and duration for the instrument to play, we want it to start when we turn it on and to keep playing until we turn it off. To do so we tell the loop to start from sample 0 and we don't give it a to sample number where to end on. To make the loop finish we have to call the function loop-finish. This is done only when the value read from the simp-on control is 0.0 (off). The function control is used to read or set the value of a controller, in this case, when we read a value of 0.0 we turn the loop off.

Inside the run-loop we set the frequency of the oscillator using the frequency function and reading the value to set from the frequency control frqf. This is a filter controller (it returns a filtered step function) so we use the fcontrol function to read its value. Finally we scale the output of the oscillator by the value returned by our amplitude control ampf and we send the signal to the output.

Next step, before creating the graphic controllers for our instrument, is to allocate memory for them using the function open-controls. This function takes only one argument, the size of the allocated portion of memory for the controllers:

(open-controls 2048)
Now we are ready to define our control panel. Using the make-controller function we can assemble a set of graphic controllers for our instrument:
(make-controller "simp_pins_1" 2048
   ;;; toggle button (defaults to t)
   '(simp-on "play" :toggle t)    
   ;;; slider going from 0.0 to 1.0 for amplitude control
   '(simp-amp "amplitude" :slider 0.0 1.0)   
   ;;; slider going from 20.0 to 11025.0 for frequency control
   '(simp-frq "frequency" :slider 20.0 11025.0))

The first parameter passed to the make-controller function is the name of the file that will contain the controllers graphic program (a good style for this is using the same name of your instrument changing hyphens by underscores), this file will be created and compiled in your home directory. The second parameter is the size of the allocated memory for the controllers (this value must be the same as the allocated before with the open-controls function). The rest of the arguments are quoted lists of this form: (index name &rest description). The index is the control location associated with the controller (the ones defined as global variables at the beginning of the code), name is the label that will appear on the screen beside the widget, and description describes the type of widget you want. In our case the first controller is a toggle button, the only parameter we pass to it is its initial state: T or on (note that this controller will return 1.0 when is on and 0.0 when is off, not boolean true of false values are passed). The amplitude and frequency controllers are defined as sliders, in this case we pass the widget its lower and upper boundaries.

Running your instrument

Once the controller program is compiled you can go to a Xterminal and run it in the background typing:
simp_pins_1 &
this will make the control panel appear on the screen. Now you can run your instrument in the Lisp listener:
(with-psound ()(simp-pins-1))
You can play around with the amplitude and frequency controls and change these parameters while listening to a continuous sinusoidal sound. When you think it's enough you can turn the instrument off clicking the play button (this will free your Lisp listener). If you are done with your instrument and don't want to play it any more a good idea is to kill the control panel (click on the rightmost button on its window bar) and, in the Lisp listener, deallocate the memory reserved for the controllers:
(close-controls)



©1998 by Juan Pampin,
juan@ccrma.stanford.edu