COMMON LISP MUSIC

Common Lisp Music (CLM, for lack of a catchier name) is a music synthesis and signal processing package in the Music V family, written primarily in Common Lisp.  The CLM instrument design language is a subset of Common Lisp (its numerical functions, and nearly all its control functions), extended with a large number of "unit generators": oscil, env, table-lookup, and so on.  The instruments can run as straight Common Lisp software, or can be compiled into C or DSP code.   Since CLM  instruments are Lisp functions, a CLM note list is just any Lisp expression that happens to call those functions.  The notes need not even be in any order.  The actual computation is done one note at a time, and the results are overlayed, building up the sound note by note.  CLM is available free, via anonymous ftp (pub/Lisp/clm.tar.Z at ccrma-ftp.stanford.edu).

Originally CLM was intended primarily for machines that had a 56000 available, but over the years the main processors have caught up and passed the 56000 in computational speed, so the current version is trying to keep a foot in both camps.  If you're running a NeXT with an Ariel QP board, you should probably stick with the 56000 version of CLM, but otherwise I suggest using the C version Ð even on the NeXT the latter is almost as fast as the former, if you're using just the built-in 56000.  The choice can be made in all.lisp when CLM is compiled and loaded, or can be delayed to instrument compilation time (the two forms can co-exist without problem), or can be left up to CLM (it will try to use the 56000, if available, except when it thinks C will perform better).  The file README.clm contains some benchmarks comparing various hardware platforms.  On the newer processors, even relatively complex instruments can run in "real-time", so I may re-implement the real-time control portion of CLM.  There are a variety of small differences between current implementations of CLM Ð for example, due to the stupidity of AKCL's defentry function, CLM's instrument-let only works in AKCL for Lisp or the 56000, but not for C.  Similarly, funcall is supported in Lisp and on the 56000, but not in C.  Most such differences are minor, and there's always a way around them.  But, to keep this document from expanding beyond anyone's patience, I've abbreviated such gotchas as [DSP] = only on the 56000, [C] = only in C, [ACL] = only in Franz's Allegro CL, [KCL] = only in AKCL, and [MCL] = only on the Mac.  Others may pop up as I sit and type.

CLM has several sections: the "unit generators", instruments (definstrument, *.ins, and ins.lisp), examples of note lists (with-sound, *.clm), a "make" facility for sound files (mix), and various functions that are useful in sound file work (sound-let, fasmix, etc).   The rest of this document concentrates on the functions CLM provides to the composer at this "top level".  The file ins.lisp is a tutorial on writing instruments.  I haven't yet written a tutorial for composition with CLM.  

Currently, CLM runs on the NeXT, Macintosh, SGI, and Intel machines running NeXTStep.  It can take advantage of Ariel's QuintProcessor board on the NeXT, and the Ariel PC56-D and Ilink i56 boards on the 486-style machines.  For benchmarks comparing these platforms, and comparing CLM to its friends in the C world (CSound, CMix, and CMusic) see the clm file README.clm.

Contents:  	Introduction		2
	Instruments		3
	        Lisp Functions (Run)	4
	        Unit Generators		5
	Sound Processing (Mix)	24
	Definstrument		26
	Note Lists (With-Sound)	28
	Phrasing			33
	Structs			35
	Appendices		38
		How to use CLM outside with-sound		38
		Fast sound file mixing and format changes		39
		Header and data types supported in CLM		40
		Low level debugging aids			40
		Sources of unwanted noise			40 
	Index			41

INTRODUCTION

CLM is a sound compiler.  It provides a Lisp environment in which various kinds of expressions generate sound files.  Lisp from time immemorial has presented itself to the user as an "interpreter".  As you type in its window, it reads what you type, tries to make sense of it, and does something, returning a result.  To use CLM either from the lisp listener or from Rick Taube's Common Music you first need to learn the rudiments of Lisp.  The basic idea in Lisp is that something of the form

    (name arg1 arg2)

calls the function "name" and passes it the two arguments.  In C this would be

    name(arg1,arg2);

In C, 4+3 is an expression yielding 7.  In Lisp this is expressed (+ 4 3).  Similarly C's 4+3*2 becomes (+ 4 (* 3 2)).  To learn Lisp, just follow the examples given below or in ins.lisp, and try making simple changes.  For full information see "Common Lisp" by Steele, "Lisp" by Winston and Horn, or "Common Lisp" by Touretzky.

The easiest way to create a new sound file is to use the macro with-sound.  Say we want to hear one second of the fm violin (in v.ins, named fm-violin) at 440 Hz, and a somewhat soft amplitude.  Load v.fasl (or v.o in KCL);(it is automatically included in the Common Music image at CCRMA), Then type

    (with-sound () (fm-violin 0 1 440 .1))

and the note should emerge from the speakers.  Once loaded, we don't need to reload v unless we change it in some way.  To get a chord:

    (with-sound () (fm-violin 0 1 440 .1) (fm-violin 0 1 660 .1))

To get a reverberated version, load a reverberator (jcrev.fasl from jcrev.ins, for example), and

    (with-sound (:reverb jc-reverb) (fm-violin 0 .1 440 .2))

The with-sound macro takes a number of optional arguments Ð :reverb is one of them.  It gives the name of the reverberator (jc-reverb in the case above).  The favorite reverberator for the last few years has been nrev in nrev.ins.  If you want to hear the sound again without recomputing it, call the function (dac). To stop in the middle of playback, type (stop-dac).  The (dac) call starts up a background process playing the sound and returns to the lisp listener.  The function dac without any argument plays the last file clm created (normally /zap/test.snd, the default output of the with-sound macro).  If you want to play some other sound file, give the file name as the argument to dac:  (dac "saved.snd").

A large number of instruments are already compiled and ready for use Ð see ins.lisp for a tutorial on building instruments, and the various *.ins files which usually contain a few sample calls.  For more examples of note lists, see the various files /dist/lisp/clm/*.clm.  Any Lisp-ism is ok in the note list:

    (with-sound ()   
      (loop for i from 0 to 7 do
        (fm-violin (* i .25) .5 (* 100 (1+ i)) .1)))

creates a little arpeggio of slightly overlapping notes.  clm-example.lisp shows how to create such a note list algorithmically.  Judicious use of mix and with-mix (page 23) can speed up the computation by an arbitrary amount.  The rest of this document discusses clm instrument structure, the built-in unit-generators, various useful sound file functions (mix, definstrument, with-sound, and local versions thereof), the 56000/C library, the "phrasing" mechanism, and the structs that make the innards of the generators available at run-time.

CLM  INSTRUMENTS

An instrument is a lisp function that can send its output to any currently open output file, and get input from any currently open input file (or from the output files, for that matter).  If you're willing to run entirely in Lisp software, there's no limitation on what you can do Ð you are just running an arbitrary lisp function.  If you are hoping to compile the lisp code into DSP or C code, there are a number of limitations (life is short, and many of Lisp's functions are not used in any current instruments).  The main two instrument-defining entities are definstrument and run. Definstrument replaces Lisp's defun, and run marks the "run-time" portion of the instrument Ð€the portion that we want to optimize as much as possible for speed.  In its simplest use, definstrument looks just like defun.  Here is a simple instrument:

   (definstrument simp (start duration frequency amplitude)
    (let* ((beg (floor (* start sampling-rate)))
           (end (+ beg (floor (* duration sampling-rate))))
           (sinewave (make-oscil :frequency frequency)))
      (Run (loop for i from beg to end do
              (outa i (* amplitude (oscil sinewave)))))))

This creates an oscillator (make-oscil), then calls it once on each sample between the start and end points, adding its output into the current output file via outa.  Once compiled and loaded, we can call this instrument:

    (with-sound () (simp 0 1 440 .1))

to produce a 1 second sine wave at 440Hz.  The normal structure of an instrument is

    (definstrument name (args)
       setup code
        (run
          (loop for i from beg to end do
             run-time code
             )))

The run macro is our dsp/C-compiler.  Use only one call on run per instrument Ð our run-time loader is not yet smart enough to thread its way through more than one dsp code vector.  See sound-let and instrument-let (and expsrc.ins.lisp) for ways to get around this limitation.

[DSP: If the instrument uses delay lines, it should end with (end-run) to free up the lines.]

LISP FUNCTIONS that RUN can handle 

The following Lisp functions can occur inside the run loop:

    +  /  *  -  1+  1-  incf decf setf setq psetf psetq
     =  /=  <  >  <=  >=  zerop  plusp  minusp  oddp  evenp  max  min  abs  mod  rem  gcd  lcm
    floor  ceiling  round  truncate  signum  sqrt  random  float
    ash  log  expt  exp  sin  cos  tan  asin  acos  atan  cosh  sinh  tanh  asinh  acosh  atanh 

    or  and  not  null   if  unless  when  cond   progn  prog1  prog2  case  tagbody  go 
    break  error  warn  print  princ  terpri  y-or-n-p  yes-or-no-p
    block  return  return-from   let  let*  loop  do  do*  dotimes declare  lambda  apply  funcall  
   aref  elt  array-total-size  array-in-bounds-p array-rank array-dimension  eref  nth

    logior  logxor  logand  lognot  logeqv  lognand  lognor  logandc1  logandc2  logorc1  logorc2  logbit  logtest 

Within the outer loop body, loop is expanded as a macro and anything in the loop syntax is ok if it expands into something else mentioned above (i.e. a lambda form with go's and so forth).  The outermost loop, however, is special Ð it is expected to be the first thing run sees, but cannot be anything but a simple loop (just one iteration variable, counting by 1 etc).

Generally user variables are assumed to be either integer, short-float, one of the struct types defined in mus.lisp (see pages 35-37), or a user-defined struct that has been defined for run's benefit with def-clm-struct (pages 38-39).  Variables are currently assumed to be of one type (that is, I haven't yet fully implemented Lisp's run-time typing).  [DSP: The loop control variable is a long integer Ð not a "normal" integer, and any arithmetic on it returns a long integer.  It should only be used where you want a sample counter result.]  Lisp's declare can be used to force a variable to be a particular type.  Run currently recognizes the types fixnum, float, and bignum Ð the latter should be used for long integers (see ins.lisp for an example).   Very large arrays (or wave-tables) should be written out as  sound files and read using ina or readin.  

[DSP: Funcall provides an escape to Lisp Ð (funcall #'some-function some-args...) passes the arguments (dsp run-time numerical values) to the function "some-function" in the Lisp interpreter, and any numerical result is passed back to the dsp.  This escape is 1000 times slower than a normal dsp function call.   Break drops into both the DSP breakpoint handler and the lisp debugger;  error halts the DSP and drops into the Lisp debugger; warn prints a warning;  print and princ print a string or a variable's value.  All but print and princ can take the usual optional formatting commands and arguments.  If the optimize quality safety is set to be greater than 0 (the default), run inserts code to catch and report overflows Ð these affect mainly those unit generators that require "fractional" input on the 56000].  In addition, the function clm-print stands in for Lisp's print+format Ð we don't support all of format's options, but enough to be useful, I hope.  Its syntax is (clm-print  format-string &rest args).

"UNIT GENERATORS"

You could stop reading here, and work directly with the mathematical description of the instrument's behavior translated into Lisp, but if you take this approach you have to write out the code to handle each algorithm every time it appears in the formula.  And the algorithm is not just the simple mathematical function call that you would naively plug into your code.  Mathematicians assume that numbers (arguments to sin for example) run to infinity and have infinite precision.  The computer's numbers run to some relatively small maximum number and the step size between successive numbers depends on a variety of factors.  If you shrug and take the most inclusive representation available, you not only lose in computation time, sometimes by many orders of magnitude, but you still hit these numerical limits in normal musical situations.  As the phase increments into the stratosphere, for example, your sin call starts to wobble, and spectra fall apart.   In addition, many of these functions are quite complex, and it would be crazy to repeat all that code every time the need for it arises.  So nearly every computer music implementation, CLM included,  provides highly optimized versions of the synthesis and processing functions that have proved most useful to date.  We follow the ancients and call them  "unit generators" Ð weird inexplicable jargon, but at least we no longer talk about "slew rate" and "mag".

Each generator consists of a pair of functions.  Make-<gen> sets up the data structure associated with the generator at initialization time, and <gen> runs the generator, producing a new sample each time it is called.  For example,

    (setf oscillator (make-oscil :frequency 330))

prepares a structure (of type osc in this case), ready to produce a sine wave when set in motion via

    (oscil oscillator)

All generators follow this calling sequence.  The initialization function normally takes a number of optional arguments, setting whatever state the given generator needs to operate on.  The run-time function's first argument is always its associated structure.  Its second argument is nearly always something like an FM input Ð whatever run-time modulation might be desired.  Amplitude envelopes are handled with a separate env generator, and attack and decay characteristics are handled by divseg at initialization time. Frequency sweeps of all kinds (vibrato, glissando, breath noise, FM proper) are all forms of run-time frequency modulation.  So, in normal usage, our oscillator looks something like:

    (oscil oscillator (+ vibrato glissando frequency-modulation))

All these functions and structures are defined in mus.lisp.  In the documentation below, we give the calling sequences and defaults, and mention any peculiarities that come to mind.  Frequencies are always in cycles per second (also known as Hz), internal table size is (nearly) always two pi.  The fm (or frequency change) argument is assumed to be a phase change in radians, applied on each sample.  Normally composers would rather think in terms of Hertz, so the function In-hz can be used to convert from units of cycles per second to radians per sample (this conversion factor used to be called "mag").  Since all the generators agree that their internal period length is two-pi, you can always use In-hz to convert the frequency change (or fm) argument from a more easily interpreted value.

    OSCIL

Oscil produces a sine wave with optional frequency change (i.e. fm).  Its first argument is an osc structure, normally created by make-oscil.  Oscil's second (optional) argument is the current (sample-wise) frequency change (defaults to 0).  Its third argument is the (sample-wise) phase change (in addition to the carrier increment and so on).  See fm.wn for a discussion of fm.

        make-oscil  &key (frequency 440.0)	; Hz
                                         (initial-phase 0.0)	; radians (to convert degrees to radians,
				;               multiply by .0174532925)
        oscil  os  &optional (fm-input 0.0)  (pm-input 0.0)

For example, a simple-FM instrument is:

(definstrument fm (beg end freq amp mc-ratio index
             &optional (amp-env '(0 0 50 1 100 1)) (index-env '(0 1 100 1)))
  (let* ((cr (make-oscil :frequency freq))
         (md (make-oscil :frequency (* freq mc-ratio)))
         (fm-index (in-Hz (* index mc-ratio freq)))
         (ampf (make-env :envelope amp-env :scaler amp :start beg :end end))
         (indf (make-env :envelope index-env :scaler fm-index
                         :start beg :end end)))
    (Run
      (loop for i from beg to end do
        (outa i (* (env ampf)
                   (oscil cr (* (env indf) 
                                (oscil md)))))))))

Oscil is essentially:  returned-val = sin(current-phase+pm-input); current-phase += freq+fm-input;

    ENV

In CLM, an envelope is a list of break point pairs:  '(0 0 100 1)  is a ramp from 0 to 1 over an x-axis excursion from 0 to 100.  This list is passed to make-env along with the desired start and end times, the scaler applied to the y axis, and the offset added to every y value.  The resulting object, when accessed with the env generator, creates an envelope of the same shape as the original, but stretched along the x-axis to fit the desired start time and duration, and transformed along the y-axis by offset + scaler * y-value.  In normal use, the x-axis can be anything you like.  Here at CCRMA, we use 0..100, but it might make more sense to use 0..1.  The kind of interpolation used to get y-values between the break points is determined by the envelope's base.

        make-env &key envelope	; list of x,y break-point pairs
                                     start-time	; in seconds
                                     duration	; seconds
                                     start		; in samples (can be used instead of start-time)
                                     end		; in samples (can be used instead of duration)
                                     (offset 0.0)	; value added to every y value
                                     (scaler 1.0)	; scaler on every y value (before offset is added)
                                      base		; type of connecting line between break-points
                                      op		; function connecting break-points (Lisp only)
        env  e
       restart-env e			; return to start of envelope

Make-env produces a structure that env can handle at run-time.  Each call on env gets the next value of the envelope, so unless you're being very clever, you should only use each envelope structure once within the run body (that is, make separate envelopes for each occurrence of env, even if everything is the same, or alternatively, use let or let* to hold the envelope value).

With the arguments to make-env, you can apply a given shape to any situtation.  Say we want  a ramp moving from .3 to .5, starting at 1.0 seconds, and ending a second later.  The corresponding make-env call would be 

     (make-env :envelope '(0 0 100 1) :scaler .2 :offset .3 
             :start-time 1.0 :duration 1.0)

The x and y axis limits of the envelope definition are arbitrary Ð use whatever limits you find easy to visualize.  When the envelope is actually applied to a specific situation, the x axis is stretched to fit the current duration and set to start at the start time.  The y axis is scaled by scaler and offset by offset.

Base determines how the break-points are connected.  If it is 1.0 (the default), you get straight line segments.  Base = 0.0 gives a step function (the envelope changes its value suddenly to the new one without any interpolation).  Any other value becomes the exponent of the exponential curve connecting the points.  Base < 1.0 gives convex curves (i.e. bowed out), and Base > 1.0 gives concave curves (i.e. sagging).  [DSP: Extreme exponentials run out of bits on the dsp Ð create a line segment envelope as close as possible to the curve you want, then use exponential curves to connect those break-points.]  

Op is not implemented on the dsp, but in the lisp software version it is the name of an arbitrary function to call to provide any kind of connecting curve you want.

Although they may appear otiose, Start-Time and Duration are not entirely useless Ð an envelope can have any duration (and perhaps someday any start time).  If its duration is shorter than the instrument's, the envelope sticks at its final value.  If the envelope's duration is longer than the instrument's, you only march through the portion of the envelope that happens to fit within the instrument's duration.  Restart-env causes an envelope to start all over again from the beginning.  [DSP: To be able to restart an envelope on the dsp, set the argument restartable to env to t (all envelopes are automatically restartable in Lisp/C)].

        divseg  fn  old-att  new-att  &optional  old-dec  new-dec
       divenv  fn  dur  &optional  p1  t1  p2  t2  p3  t3  p4  t4  p5  t5

Divseg provides attack and decay controls on an envelope.  Very often, the envelope is considered to have two or three independent portions.  The first, called the "attack", is very important, and its duration needs to be dealt with independently of the second ("steady state") and third ("decay") portions.  Divseg takes the original envelope (Fn), the original x axis point at which the attack portion ends, the new x-axis point where it should end, and similar data for the decay portion if desired, and returns the new envelope that has been squeezed or stretched along the x-axis to produce the desired timings.

    (divseg '(0 0 100 1) 50 25) => '(0 0 25 .5 100 1)
    (divseg '(0 0 100 1) 50 25 75 90) => '(0 0 25 .5 90 .75 100 1)

Divenv provides the same operations as divseg, but gives you up to five segments to push around, and instead of specifying the new x axis values, you supply the times (in seconds).  Divenv can be extremely confusing Ð it was a kludge in previous CCRMA software to get around severe memory limitations and to avoid inadequate envelope definition software.  I urge you to make the envelopes from scratch.  For example, say we want the basic shape '(0 0 1 1 2 1 3 0) to rise to 1 in the time att, and decay in the time dec, where the overall duration is dur.  All it takes it (list 0 0 att 1 (- dur att dec) 1 dur 0) as the envelope argument to make-env.  Surely this is clearer than the equivalent (divseg '(0 0 1 1 2 1 3 0) 1 (* 3 (/ att dur)) 2 (* 3 (/ (- dec att dec) dur)))?

env.lisp has a variety of functions that operate on envelopes. The most useful are:

  env-reverse		reverse an envelope
  env-repeat		repeat an envelope
  env-concatenate	concatenate any number of envelopes
  env+		add together any number of envelopes
  env*		same but multiply
  env-max		same but min
  env-simplify		simplify an evelope (smoothing using graphics notions)
  fft-env-simplify		same but use fft-filtering to smooth
  meld-envelopes	return attempt to meld two envelopes together
  map-across-envelopes	map a function across any number of envelopes

mus.lisp has a few other useful, self-evident envelope functions:  list-interp, scale-envelope, and normalize-envelope.

    OUTA  OUTB  OUTC  OUTD

        outa  loc  data  &optional  (o-stream  *current-output-file*)
        outb  loc  data  &optional  (o-stream  *current-output-file*)
        outc  loc  data  &optional  (o-stream  *current-output-file*)
        outd  loc  data  &optional  (o-stream  *current-output-file*)

Outa and friends merge Data into O-Stream at sample position Loc.  O-Stream defaults to the current default output file.  Data is a short-float, normally between -1.0 and 1.0 Ð the sound file contains 2's complement integers interpreted as fractions between -1 and 1, but not including 1 itself.  [DSP: clm's output functions truncate data, so if you (outa 0 1.0), the actual value written is 0].   Locsig is the recommended way to do multi-channel output.

    IN-A  IN-B  IN-C  IN-D  INA  INB

       in-a  loc  &optional  (i-stream  *current-input-file*)
        in-b  loc  &optional  (i-stream  *current-input-file*)
        in-c  loc  &optional  (i-stream  *current-input-file*)
        in-d  loc  &optional  (i-stream  *current-input-file*)

In-a and friends get the sample at position Loc in I-Stream as a short-float (normally between -1.0 and 1.0).  I-Stream defaults to the current default input file.  Ina and inb are the same as in-a and in-b Ð "Ind" got too many loader errors, so I added the "-".

    REVIN  REVOUT

        revin  i
        revout  i  val

Revin and revout read/write data from/to the reverb stream. In the clm software, reverb is normally handled as a third independent channel, and the actual reverberation is done after all other processing is complete.  The current reverb stream is *reverb*.

    READIN   READIN-REVERSE

         make-readin  &key  file  start-time  start  (channel  :A)
         readin  rd

         make-reverse  &key  file  start-time  start  (channel :A)
         readin-reverse  rd

Readin provides a way to package up information related to a given input stream (File, Channel, and Start-Time (seconds) or Start (samples)).  The field rdin-i is the sample counter (see under Structs below).  Input-file-start-time and input-file-start are synonyms for start-time and start.

Here is an instrument that applies an envelope to a sound file using readin and env:

(definstrument env-sound (file beg &optional (amp 1.0) 
                                             (amp-env '(0 1 100 1)))
  (let ((f (open-input file)))
    (unwind-protect
      (let* ((st (floor (* beg sampling-rate)))
        (dur (clm-get-duration f))
        (rev-amount .01)
        (rdA (make-readin :file f :input-file-start-time 0.0))
        (rdB (if (and (stereo f) (stereo *current-output-file*))
                 (make-readin :file f :input-file-start-time 0.0 :channel :B)))
        (ampf (make-env :envelope amp-env :scaler amp :start-time beg :duration dur))
        (nd (+ st (floor (* sampling-rate dur)))))
    (Run
      (loop for i from st to nd do
        (let* ((amp-val (env ampf))
               (outa-val (* amp-val (readin rdA))))
          (outa i outa-val)
          (if rdB (outb i (* amp-val (readin rdB))))
          (if *reverb* (revout i (* rev-amount outa-val)))))))
    (close-input f))))

To change direction in the run loop, you can either set rdin-inc (see page 36), or call read-forward or read-backward.  The current readin sample number is returned by read-position.

    LOCSIG

         make-locsig  &key  (degree 0.0)  (distance 1.0)  (revscale 0.01)  revin
         locsig  loc  i  in-sig

Locsig normally takes the place of outa and outb in an instrument.  It tries to place a signal between outa and outb in an extremely dumb manner Ð it just scales the respective amplitudes ("that old trick never works").  Revscale determines how much of the direct signal gets sent to the reverberator.  Distance tries to imitate a distance cue by fooling with the relative amounts of direct and reverberated signal (independent of Revscale).  Locsig is a kludge, but then so is any pretence of placement when you're piping the signal out a loudspeaker.  It is my current belief that locsig does the "right" thing for all the wrong reasons Ð a good concert hall provides "auditory spaciousness" by interfering with the ear's attempt to localize a sound Ð that is, a diffuse sound source is the ideal!  By sending an arbitrary mix of signal and reverberation to various speakers, locsig gives you a very diffuse "source" Ð it does the opposite of what it claims to do, and by some perversity of Mother Nature, that is what you want.  (See "Binaural Phenomena" by J Blauert).

If you'd like to mess around with room simulations, see the function roomsig in mus.lisp with examples in room.ins.  Locsig now handles quad output Ð the speakers are assumed to be in a circle with channel A at 0, channel B at 90, and so on.

    TABLE-LOOKUP

Table-lookup performs interpolating table lookup.  Indices are first made to fit in the current table (fm input can produce negative indices), then linear interpolation returns the table value.  Table-lookup scales its frequency change argument (fm-input) to fit whatever its table size is (that is, it assumes the caller is thinking in terms of a table size of two pi, and fixes it up).  The wave table should be an array of floats (short-floats, preferably).  The function make-table returns such an object, ready to be loaded.

        make-table-lookup  &key  (frequency 440.0)		; in Hz
                                                         (initial-phase 0.0)		; in radians 
                                                         wave-table		; short-float array with wave

         table-lookup  tl  &optional  (fm-input  0.0)
         make-table  &optional  (size default-table-size)

There are several functions available that make it easier to load up various wave forms:

         load-synthesis-table  synth-data  table  &optional  (norm  t)
         load-synthesis-table-with-phases  synth-data table  &optional  (norm t)

The synth-data argument is a list of (partial amp) pairs:

    '(1 .5 2 .25)

gives a combination of a sine wave at the carrier (1) at amplitude .5, and another at the first harmonic (2) at amplitude .25.  The partial amplitudes are normalized to sum to a total amplitude of 1.0 unless the argument norm is nil.  If the initial phases matter (they almost never do), you can use load-synthesis-table-with-phases.  The phases are in radians.  [DSP: it takes a long time both to load the table, and to write the table contents to the 56000's private memory, so additive synthesis should be done with waveshaping, not table lookup.]

/dist/lisp/clm/spectr.clm has a large number of steady state spectra of standard orchestral instruments, courtesy James A. Moorer.  /dist/lisp/clm/bird.clm has about 50 North American bird songs.  Very large tables should be handled as sound files and read with ina and friends.

    RANDH   RANDI

Randh and randi produce random numbers of various kinds.  The Lisp function random is also available.

        make-randh  &key  (frequency  440.0)	;freq at which new random numbers occur
                                            (amplitude  1.0)	;numbers are between 0.0 and amplitude
                                            (increment  0.0)
                                            (type  'random)
         randh  r  &optional  (sweep  0.0)

         make-randi  &key  (frequency 440.0)  (amplitude 1.0)
         randi  r  &optional  (sweep  0.0)

Randh returns a sequence of random numbers between 0.0 and its amplitude (it produces a sort of step function Ð David Mellinger suggests that the "h" stands for "hold").  Randi interpolates between successive random numbers.

[DSP: If Type is 'Sambox-random, you get a simulation of the Samson box's random number generator.]

The "central-limit theorem" says that you can get closer and closer to gaussian noise simply by adding randh's together.

    ARRAY-INTERP, TABLE-INTERP, DOT-PRODUCT

These simple functions underlie some of the unit-generators, and can be called within Run, if you want to roll your own.  See mus.lisp for details.

    SAWTOOTH-WAVE
    TRIANGLE-WAVE
    PULSE-TRAIN
    SQUARE-WAVE

These generators produce some occasionally useful wave forms.  Sawtooth-wave ramps from -1 to 1, then goes immediately back to -1.  Triangle-wave ramps from -1 to 1, then ramps from 1 to -1.  Pulse-train produces a single sample of 1.0, then zeros.  Square-wave produces 1 for half a period, then 0.  All have a "period" of two-pi, so the fm argument should have an effect comparable to the same fm applied to the same waveform in table-lookup.

         make-triangle-wave  &key  frequency  (amplitude  1.0)  (initial-phase  pi)
          triangle-wave  s  &optional  (fm  0.0)

          make-square-wave  &key  frequency  (amplitude  1.0)  (initial-phase  0)
          square-wave  s  &optional  (fm  0.0)

          make-sawtooth-wave  &key  frequency  (amplitude  1.0)  (initial-phase pi)
          sawtooth-wave  s  &optional  (fm  0.0)

          make-pulse-train  &key  frequency  (amplitude 1.0)  (initial-phase two-pi)
          pulse-train  s  &optional  (fm 0.0)

Triangle-wave appears almost exclusively in the vibrato computation (v.ins for example).  One popular kind of vibrato is:

    (+ (triangle-wave pervib) (randi ranvib))

    ONE-POLE
    ONE-ZERO
    TWO-POLE
    TWO-ZERO

These are simple filters.

         make-one-pole  a0  b1		; b1 < 0.0 gives lowpass, b1 > 0.0 gives highpass
         one-pole  f  input
         make-one-zero  a0  a1		; a1 > 0.0 gives weak lowpass, a1 < 0.0 highpass
         one-zero  f  input
         make-two-pole  a0  b1  b2	; see ppolar below
         two-pole  f  input
         make-two-zero  a0  a1  a2	; see zpolar below
         two-zero  f  input

The difference equations are:

        one-zero    y(n) = a0 x(n) + a1 x(n-1)
        one-pole    y(n) = a0 x(n) - b1 y(n-1)
        two-pole    y(n) = a0 x(n) - b1 y(n-1) - b2 y(n-2)
        two-zero    y(n) = a0 x(n) + a1 x(n-1) + a2 x(n-2)

(This nomenclature is taken from Julius Smith's "An Introduction to Digital Filter Theory" in Strawn "Digital Audio Signal Processing", and is different from that used in more general filters given on page 15.  It is also different from that used by the MusicKit.  For example, the MusicKit's one-pole's A1 is our b1 and their B0 is our a0).  The coefficients should be between -2.0 and 2.0.   

    PPLOAR
    ZPOLAR
    FORMNT

These generators provide a more intuitive handle on the simple filters given above.

         make-ppolar  R  freq
         ppolar  f  input		; bandpass, centered at freq, bandwidth set by R

         make-zpolar  R  freq
         zpolar  f  input 		; bandstop, notch centered at freq, width set by R

         make-formnt  R  freq  &optional  G
         formnt  f  input 		; resonator centered at freq, bandwidth set by R

PPolar    y(n) = x(n) + 2*R*cos(2*pi*Freq/Srate)*y(n-1) - R*R*y(n-2)
ZPolar    y(n) = x(n) - 2*R*cos(2*pi*Freq/Srate)*x(n-1) + R*R*x(n-2)
Formnt    y(n) = x(n) - R*x(n-2) + 2*R*cos(2*pi*Freq/Srate)*y(n-1) - R*R*y(n-2)

PPolar ("poles polar") allows you to specify the radius and angle of a pole while two-pole requires actual filter coefficients.  The filter provided by ppolar has two poles, one at (R,Freq), the other at (R,-Freq).  R is between 0 and 1 (but less than 1), and Freq is between 0 and Srate/2.  This is the standard resonator form with poles specified by the polar coordinates of one pole.  Similar remarks apply to two-zero and zpolar.  More than likely you want "polar-coordinate" calling parameters (R and Freq) when setting up a second order section.  The "rectangular-coordinate" versions exist for generality (e.g. to make two real poles) and are rarely needed.

The impulse response of a two-pole filter section is an exponentially decaying sinusoid:

        Rn * cos(2*pi*(Freq/Srate)*n+Phi)

where (R,Freq) are as above, and Phi is a phase term.  Therefore, the time constant of decay is 1/(1-R) samples which is 1/((1-R)*Srate)) seconds.  Seven time constants is approximately the time it takes to decay 60dB.  For example, if R=0.9999, then the time constant of decay is 10000 samples.  If Srate=50000, then 10000 samples is one fifth of a second.  Seven time-constants is then a little more than a second.  Thus, the two-pole filter specified by ppolar with R=0.9999 and Freq=400, with Srate=50000, is a resonator which when pulsed will give an exponentially decaying tone at A440 which decays for about a second before becoming inaudible.

Formnt is recommended for resonators (simple bandpass filters), vocal tract or instrument cavity simulations, etc.  It provides robust bandpass filtering (simple resonance) using two poles and two zeroes.  Only one coefficient need be changed in order to move the filter center frequency.  The filter coefficients are set as a function of desired pole-radius R (set by desired bandwidth), center frequency F, and peak gain G.

Here's a little instrument that uses formnt to filter white noise:

(definstrument simp (beg end R freq G)
  (let* ((amp .5)
         (f (make-formnt R freq G))
         (noi (make-randh :frequency 5000 :amplitude .1)))
    (Run
      (loop for i from beg to end do
        (declare (optimize (safety 1)))   ;catch and report overflows
        (outa i (* amp (formnt f (randh noi))))))))

(With-sound () (simp 0 10000 .99 1000 1.0))

    FILTER
    DIRECT-FILTER
    LATTICE-FILTER
    LADDER-FILTER

These are the general FIR/IIR filters of arbitrary order.  [DSP: all coefficients must be between  -1.0 and 1.0].

         make-filter  &key  order
                                          m		; same as order
                                          a		; same as y-coeffs
                                          p 		; same as x-coeffs
                                          x-coeffs
                                          y-coeffs
                                          (type direct-form)
                                          sn		; same as a, for ladder-filter
                                          rc		; for lattice-filter
                                          tap		; same as p, for ladder-filter or lattice-filter
                                          k		; same as rc, for lattice-filter
                                          cn		; for ladder-filter

         make-direct-filter  &key  a  p  m
         make-lattice-filter  &key  k  rc  tap  p  a  m
         make-ladder-filter  &key  sn  cn  a  p  tap  so  m

         direct-filter  fl  inp
         lattice-filter  fl  inp		; Markel & Gray's "two multiplier lattice"
         ladder-filter  fl  inp		; M&G's "normalized ladder"
         filter  fl  inp

Here we follow the nomenclature of Markel and Gray, "Linear Prediction of Speech".  Many of the keyword arguments are synonyms Ð you can use whichever name you're used to.  If you give x-coeffs or y-coeffs, but ask for a lattice or ladder-filter, CLM automatically translates to the correct reflection coefficients.  Use flt-a and flt-b as arrays if you want time-varying coefficients.  We follow Markel and Gray in assuming that the first  y-coeff  is 1.0.  That is, to implement the difference equation yn <= a0*x0 - b1*y1 we would set y-coeffs to (list 1.0 b1) and x-coeffs to (list a0).  The possible types are direct-form, ladder-form, and lattice-form.

See fltdes.lisp for functions compatible with these filters that provide the coefficients for various FIR and IIR filters.  There are also functions to print the frequency response and so on Ð this code could relatively easily be made more flexible, if anyone is interested.  fltdes.lisp also contains examples and test cases.  An example is given on page 32.

    COMB   NOTCH

         make-comb  mlt  length
         comb  cflt  input

         make-notch  mlt  length
         notch  cflt  input

Comb is a delay line with a scaler on the feedback term.  Notch is a delay line with a scaler on the feedforward term.  Mlt is the scaler (it should be less than 2.0 in magnitude).  Length is the length in samples of the delay line.

In filter parlance, comb is:     y(n) <= x(n-length-1) + mlt * y(n-length)

As a rule of thumb, the decay time of the feedback part is 7*length/(1-mlt) samples, so to get a decay of Dur seconds, mlt <= 1-7*length/(Dur*Srate).  The peak gain is 1/(1-(abs mlt)).  See Julius Smith's "An Introduction to Digital Filter Theory" in Strawn "Digital Audio Signal Processing", or Smith's "Music Applications of Digital Waveguides".

    ALL-PASS

         make-all-pass  feedback-scaler feedforward-scaler  length
         all-pass  f  input

All-pass or "moving average comb" is just like comb but with added feed-forward term.  If feedback-scaler = 0, we get a moving average comb filter.  If both scale terms = 0, we get a pure delay line.  In filter parlance,

     y(n) <= feedforward-scaler*x(n-1) + x(n-length-1) + feedback-scaler*y(n-length)

[DSP: the samples in the delay are assumed to be fractional (between -1 and 1).]

    DELAY

         make-delay   length  &key  initial-contents  initial-element
         delay  d  input
          tap  d  &optional  (offset  0)

Delay is a straightforward delay line.  Length is in samples.  Input fed into a delay line reappears at the output Length samples later.  Initial-element defaults to 0.0.  If specified, initial-contents need not be the same length as the delay line.  [DSP: input is assumed to be between -1 and 1.]  Tap returns the current value of the delay generator.  Offset is the distance of the tap from the current (default) delay line sample Ð it is assumed to be positive or zero.

    ZDELAY

         make-zdelay  (length  &key  initial-contents  initial-element  true-length)
         zdelay  z  input  &optional  (pm-input  0.0)
         ztap  z  &optional  (pm-input  0.0)

Zdelay is an "interpolating" delay line.  Zdelay is just like delay but the current index into the delay line can have a fractional part.  Pm-Input determines how far off from the normal index we are.  Obviously there are limits to what you can get away with.  Zdelay is intended for things like "flanging" where the delay line is relatively long, and the interpolation motion is relatively small.  Zdelay uses "phase modulation" rather than FM, because it is closer to what we want to think about here Ð the Pm-Input argument is difference between the nominal delay length (Length) and the current actual delay length (Length + Pm-Input) Ð a positive Pm-Input corresponds to a longer delay line.  If true-length is not set explicitly, you normally get the smaller of twice the length and the length + 512; a pm-input outside those bounds will cause trouble.  To add a tap to a zdelay line, use ztap.

(definstrument zcomb (time duration freq amp length1 length2 vib-env)
  (multiple-value-bind
      (beg end) (get-beg-end time duration)
    (let ((s (make-pulse-train :frequency freq))
          (d0 (make-zdelay length1 :true-length (max length1 length2)))
          (venv (make-env :envelope vib-env :start-time time 
                          :duration duration :scaler (- length2 length1)))
          (feedback .5))
      (run
        (loop for i from beg to end do
          (let ((dly-val (ztap d0)))
            (outa i (zdelay d0 (+ (* amp (pulse-train s))
                                  (* feedback dly-val))
                               (env venv))))))
      (end-run))))

;;; (with-sound () (zcomb 0 1 10 .99 10 300 '(0 0 100 1)))

    WAVESHAPE

        make-waveshape  &key  (frequency  440.0)  (partials  '(1 1))
        waveshape  w  &optional  (index  1.0)  (fm 0.0)

        signify  Harm-amps
        make-waveshape-table  harm-amps  &optional (norm t) (tblsiz default-table-size)
        make-phase-quad-table  harm-amps  phases  &optional  (tblsiz  default-table-size)
        get-chebychev-coefficients  partials  &optional  (kind  1)

Waveshape performs waveshaping.  For a description of waveshaping, see "Digital Waveshaping Synthesis" by Marc Le Brun in JAES 1979 April, vol 27, no 4, p250.  [DSP: it is expensive (that is, it takes a long time) to load the table and read it using waveshape.  I recommend that you use polynomial instead.]

(definstrument simp ()
  (let ((wav (make-waveshape :frequency 440 :partials '(1 .5 2 .3 3 .2))))
    (Run (loop for i from 0 to 1000 do (outa i (waveshape wav))))))

See bigbird below for an example of get-chebychev-coefficients, and pqw.ins for an example of phase quadrature waveshaping.  Signify takes a list of harmonic amplitudes and puts a pattern of signs on that list that optimizes the behaviour of the waveshaper at low indices.  Get-chebychev-coefficients takes a list of harmonic amplitudes and returns a list of Chebychev polynomial coefficients.  The argument kind determines which kind of Chebychev polynomial we are interested in.

    POLYNOMIAL

         polynomial  coeffs  x

Coeffs is an array of coefficients, x is the value to be plugged into the polynomial.  Coeffs[0] is the constant term, and so on.  For waveshaping, use the function get-chebychev-coefficients.  [DSP: the coefficients in coeffs, the argument x and all intermediate values produced during polynomial evaluation are assumed to be "fractional".]  Abramowitz and Stegun, "A Handbook of Mathematical Functions" is a treasure-trove of interesting polynomials.

(definstrument BigBird (start duration frequency freqskew amplitude
                          Freq-env Amp-env Partials)
  (multiple-value-bind (beg end) (get-beg-end start duration)
    (let* ((gls-env (make-env :envelope freq-env :scaler (In-Hz freqskew)
                              :start-time start :duration duration))
           (os (make-oscil :frequency frequency))
           (fil (make-one-pole .1 .9))
           (coeffs (get-chebychev-coefficients 
                     (normalize-partials partials)))
           (amp-env (make-env :envelope amp-env :scaler (* 8 amplitude) 
                              :start-time start :duration duration)))
      (loop for i from 0 below (length coeffs) do
        (setf (aref coeffs i) (* .125 (aref coeffs i))))
      (Run
        (loop for i from beg to end do
          (outa i 
            (one-pole fil 
              (* (env amp-env) 
                 (polynomial coeffs (oscil os (env gls-env)))))))))))

    RESAMPLE

         make-resample  &key  file (srate  1.0)  start-time  start  (channel  :A)
         resample  s  &optional  (sr-change  0.0)

Resample performs sampling rate conversion by linear interpolation.  File is the file of sound samples to be resampled, Start-Time is where to start in that file (in seconds), Start is the start point in samples, Channel is which channel to resample, and Srate is how much to move forward on each sample.  Srate = 1.0 therefore causes no change at all, Srate = .5 interpolates a point between each of the originals, so the sound goes down an octave if played at the original sampling-rate.  Similarly Srate = 2.0 moves it up an octave.  Sr-Change is added to Srate to provide time varying sampling rate changes.   Large downward shifts are often marred by the buzz or whine of quantization noise Ð use src for extreme cases.  Input-file-start-time and input-file-start are synonyms for start-time and start.

    SRC

         make-src  &key  file  (srate  1.0)  (channel  :A)  start-time  start  (width  5)
         src  s  &optional  (sr-change  0.0)

Src performs sampling rate conversion in a somewhat more sophisticated manner than resample Ð it convolves the input with a sinc function, after filtering the input if Srate > 1.0.  As in resample, File is the file to be converted, Srate is the ratio between the new sampling rate and the old, Start-Time is where to start in File (in seconds), Channel is which channel to convert, and Width is how many neighboring samples to convolve with sinc.  Input-file-start-time and input-file-start are synonyms for start-time and start.  In src, the Sr-Change argument is the amount to add to the current Srate on a sample by sample basis.  Currently, the low pass filter is not changed in concert with Sr-Change, so large upward changes may not be engineer-approved.  Here's an instrument that provides time-varying sampling rate conversion:

(definstrument simp (start-time duration amp srt srt-env filename)
  (let* ((senv (make-env :envelope srt-env :scaler 1.0 :offset 0.0
                         :start-time start-time :duration duration))
         (beg (floor (* start-time sampling-rate)))
         (end (+ beg (floor (* duration sampling-rate))))
         (f (open-input filename))
         (src-gen (make-src :file f :srate srt)))
    (Run
      (loop for i from beg to end do
        (outa i (* amp (src src-gen (env senv))))))
    (close-input f)))

    CONTRAST-ENHANCEMENT

         contrast-enhancement  in-samp  &optional  (fm-index  1.0)

Contrast-enhancement phase-modulates a sound file.  It's like audio MSG.  The actual algorithm is sin(in-samp*pi/2 + (fm-index*sin(in-samp*2*pi))).  The result is to brighten the sound, helping it cut through a huge mix and so on.

    WAVE-TRAIN

         make-wave-train  &key  wave  (frequency 440.0)  (initial-phase 0.0)
         wave-train  w  &optional  (fm  0.0)

Wave-train produces a wave train (an extension of Pulse-Train).  Frequency is the repetition rate of the wave found in Wave.  Successive waves can overlap.  With some simple envelopes, or filters, you can use this for VOSIM and other related techniques.  [DSP: the actual wave  samples are assumed to be fractional.]  See ins.lisp for a number of example instruments.

    RUN-BLOCK

         make-block  &key  size  trigger
         run-block  blk

This unit generator is the main working portion of all the block processing generators, and is provided for those who want to build their own fancy generators.  There are some examples in the ins.lisp file (phase-vocoders and so on).  A block (rblk-buf) can be cleared quickly with clear-block.

    FFT-FILTER

         make-fft-filter  &key  filter
                                               file		;input file
                                               start-time		;start-time in input file in seconds
                                               start		;same but in samples
                                               (channel :A)
                                               (fft-size 512)
          fft-filter  ff

Fft-filter performs fft based filtering of File.  Filter can be an envelope or an array.  In either case, the values should be positive fractions (that is, numbers between 0.0 and 1.0, inclusive).  The advantage of using an array is that you can change the filter on the fly in the instrument (changing an envelope is relatively difficult).  Filter represents the frequency response of the filter for the frequencies below the Nyquist limit Ð if an array is used, it should be 1/2 the Fft-Size.  [DSP: fft-size should be small enough to fit in the available DSP memory].  Input-file-start-time and input-file-start are synonyms for start-time and start.  Here's an example of using fft-filter with a table to do time-varying filtering.  In this case, we are gradually notching out some of the lower frequencies.

(definstrument fftins (beg dur spectrum file)
  (let* ((start (floor (* beg sampling-rate)))
         (end (+ start (floor (* dur sampling-rate))))
         (fil (open-input file))
         (val 1.0)
         (step (/ 1.0 (* (min dur 1.0) sampling-rate))) ;max 1 sec for ramp 
         (notch-start 10)
         (notch-end 20)
         (ff (make-fft-filter :file fil :input-file-start-time 0.0
                              :fft-size 128 :filter spectrum)))
    (Run
      (loop for i from start to end do
        (when (>= val step) ;ramp down to 0, then leave it there
          (decf val step)
          (loop for k from notch-start to notch-end do
          (setf (aref (fftflt-env ff) k) val)))
        (outa i (fft-filter ff))))
    (close-input fil)))

In this example, we are assuming that "spectrum" is an array, not an envelope (we are accessing fftflt-env using aref inside run).  Due to the stupidity of the foreign function interface the array passed must have elements of type 'short-float Ð e.g. (make-array 256 :element-type 'short-float).  (Of course, a "real" fftins would protect against interruptions and :reset and so on).  If fftflt's file is explicitly nil, the input is white noise.  This is a feature, not a bug Ð an example can be found in ins.lisp.

    CONVOLVE

         make-convolve  &key  filter
                                                  file
                                                 start-time
                                                 start
                                                 (channel :A)
                                                 (fft-size 512)
          convolve  ff

Closely related to fft-filter is the unit generator convolve.    Its arguments are the same as in make-fft-filter and fft-filter, and it provides a very similar function.  Fft-filter assumes that the imaginary part of the fft of the spectrum is 0, whereas convolve does not.  [DSP: If filter is a file, convolve falls into C and can handle any size convolution; if both files to be convolved are bigger than will fit in memory, you'll spend a lot of time swapping].  ("Filter" = impulse response here).  Input-file-start-time and input-file-start are synonyms for start-time and start.

(definstrument convins (beg dur filter file 
                        &optional (size 128))
  (let* ((start (floor (* beg sampling-rate)))
         (end (+ start (floor (* dur sampling-rate))))
         (fil (open-input file))
         (ff (make-convolve :file fil  :input-file-start-time 0.0
                            :fft-size size  :filter filter)))
    (Run
      (loop for i from start to end do (outa i (convolve ff))))
    (close-input fil))) 

    EXPAND

      make-expand  file-name			;input file name
                      &key  start-time			;start-time (secs) in the input file
                                  start			;same, but in samples
                                  (channel  :A)		;which channel this expand gen reads
                                  (segment-length . 15)		;length of file slices that are overlapped
                                  (segment-scaler  .6)		;amplitude scaler on slices (to avoid overflows)
                                  (expansion-amount  1.0)	;how much to lengthen or compress the file
                                  (output-hop  .05)		;speed at which slices are repeated in output
                                  (ramp-time .4)		;amount of slice-time spent ramping up/down
        expand  e

Expand "expands" the sound file File-name.  It is the poor man's way to change the speed at which things happen in a recorded sound without changing the pitches.  It works by slicing the input file into short pieces, then overlapping these slices to lengthen (or shorten) the result, sometimes known as granular synthesis, and similar to the "freeze" function.

    SUM-OF-COSINES

         make-sum-of-cosines  &key  (cosines  1)
                                                               (frequency  440.0)
                                                               (initial-phase 0.0)
         sum-of-cosines  cs  &optional  (fm  0.0)

Sum-of-cosines produces a band-limited pulse train containing Cosines cosines.  It uses the formula:  1+2(cos(x)+cos(2x)+...cos(nx)) = sin((n+.5)x)/sin(x/2).

    RING-MODULATE    AMPLITUDE-MODULATE

         ring-modulate  in1  in2
         amplitude-modulate  am-carrier  input1  input2

Ring-modulate returns (* in1 in2).
Amplitude-modulate returns (* input1 (+ am-carrier input2))

Since neither needs any state information, there are no associated "make" functions.

Both of these take advantage of the "Modulation Theorem" Ð since multiplying a signal by a phasor (e ^ (j w t)) translates its spectrum by w / two-pi Hz, multiplying by a sinusoid splits its spectrum into two equal parts translated up and down by w/two-pi Hz.  The simplest case is: 

        cos f1 * cos f2 = (cos (f1 + f2) + cos (f1 - f2)) / 2.

VARIOUS OTHER CLM FUNCTIONS

    stereo  &optional  (o-stream  *current-output-file*)      ;2 channels (these can occur in run)
    mono  &optional  (o-stream  *current-output-file*)        ;1 channel
    quad  &optional  (o-stream  *current-output-file*)         ;4 channels

Stereo returns t if its argument is a stereo file (i.e. has exactly 2 channels).  Mono returns t if its argument is a mono file.  The default argument for both is the current output file, also known as *current-output-file*.  (Quad and stereo can't both be true at the same time).

    get-beg-end  start-time  duration	convert Start-Time and Duration from seconds into 			  samples.
    set-srate  new-srate 		Set the sampling-rate to New-Srate.
    clm-get-channels  file		Returns the number of channels in the file File (an IO 			  struct as returned by open-input).
    clm-get-sampling-rate  file	Returns the sampling rate of File (IO struct).
    clm-get-max-amp  file		Returns the maximum amplitude of File (IO struct).
    clm-get-duration  file		Returns the duration (secs) of the file File (IO struct).
    clm-get-samples  file		Returns the number of full samples in the file File

As an example of these, here's a function that returns the maximum amplitude in a sound file:

  (defun maxamp (file) 
    (let ((hi (open-input (namestring (merge-pathnames file default-sound-file-name))))) 
      (multiple-value-bind (a b) (clm-get-max-amp hi) 
        (close-input hi) 
        (list a b))))

   clm-get-default-header

Returns a string containing the current date and time, the current version of the clm software and so on.  This is used as the default "comment" in every sound file header.
    in-Hz  Freq-in-Hz

Converts Freq-in-Hz to radians/sample (for any situation where a frequency is used as an amplitude Ð glissando or FM, for example).  It can be used within run.

    clu-reset		Close all files, close the DSP, and clean up all internal state.

    fft, inverse-fft, and fft-window

These provide run-time access to a standard fft routine.  Both take as the only argument an fft-data structure, normally created by the function make-fft-data-arrays.  Its only argument is the size of the ffts. 

(definstrument pvins (beg dur file amp shift &optional (fftsize 128))
  ;;assume shift is a positive integer
  (let* ((start (floor (* beg sampling-rate)))
         (end (+ start (floor (* dur sampling-rate))))
         (fil (open-input file))
         (fftsize-1 (1- fftsize))
         (fd (make-fft-data-arrays fftsize))
         (wtb (make-block :size fftsize :trigger 0))
         (window (make-block :size fftsize))
         (freq-inc (/ fftsize 2))
         (freq-inc-1 (1- freq-inc))
         (rate (/ 1.0 (* freq-inc fftsize)))
         (filptr 0)
         (shift-top (- fftsize shift 1)))
    (loop for i from 0 below freq-inc and j from fftsize-1 by -1 and angle from 0.0 by rate do
      (setf (aref (rblk-buf window) i) angle)
      (setf (aref (rblk-buf window) j) angle))
    (Run
      (loop for i from start to end do
        (when (zerop (rblk-loc wtb))
          (dotimes (k fftsize)
            (setf (aref (fft-data-real fd) k) (* (aref (rblk-buf window) k) (ina filptr fil)))
            (incf filptr))
        (clear-block (fft-data-imaginary fd))
        (decf filptr freq-inc)
        (fft fd)
         ;; now shift the positive frequencies up and the negative frequencies down
        (loop for k from freq-inc-1 downto shift and j from freq-inc to shift-top do
          (setf (aref (fft-data-real fd) k) (aref (fft-data-real fd) (- k shift)))
          (setf (aref (fft-data-imaginary fd) k) (aref (fft-data-imaginary fd) (- k shift)))
          (setf (aref (fft-data-real fd) j) (aref (fft-data-real fd) (+ j shift)))
          (setf (aref (fft-data-imaginary fd) j) (aref (fft-data-imaginary fd) (+ j shift))))
        (dotimes (k shift)
          (setf (aref (fft-data-real fd) k) 0.0)
          (setf (aref (fft-data-imaginary fd) k) 0.0)
          (setf (aref (fft-data-real fd) (- fftsize-1 k)) 0.0)
          (setf (aref (fft-data-imaginary fd) (- fftsize-1 k)) 0.0))
        (inverse-fft fd)
        (dotimes (k fftsize)
          (incf (aref (rblk-buf wtb) k) (aref (fft-data-real fd) k)))
        (incf (rblk-ctr wtb) freq-inc))
      (outa i (* amp (run-block wtb)))))))

The arguments to fft-window are the fft-data structure and the array containing the window values.  Many of the standard windows are available in mus.lisp and, for the enterprising, in lib56.lisp.  See ins.lisp and san.ins for more examples (vocoder tricks and so on).

Mix  file  begin  &body body		;see also fasmix (page 39)
With-mix options file begin &body body

Mix is a macro, callable within with-sound or clm-load, that saves the computation in its body in a separate file named file (without the .snd extension), and can tell when that file's data is up to date and need not be recomputed.   Mix is equivalent to open-input or mix-in (see below) with a separate file of clm instrument calls, but lets you keep all the notes in one file.  Mix with no body is the same as mix-in.  With-mix is the same as mix, but gives you a chance to specify local with-sound options (mainly for local reverb).  For example, the following with-sound uses mix, with-mix, mix-in, and normal instrument calls:

  (with-sound () 
    (fm-violin 0 .1 440 .1)
    (mix "sec1" .5 
      (fm-violin 0 .1 550 .1)
      (fm-violin .1 .1 660 .1))
    (with-mix (:reverb jc-reverb) "sec2" 1.0
      (fm-violin 0 .1 880 .1 :reverb-amount .2)
      (fm-violin .1 .1 1320 .1 :reverb-amount .2))
    (fm-violin 2 .1 220 .1)
    (mix-in "/zap/slow.snd" 2))

Now, if we change just the first note in the mix call, the with-mix section will not be recomputed, but will be mixed in from the saved file "sec2.snd".  By surrounding stable sections of a piece with calls on mix or with-mix, you can save a huge amount of time that would otherwise be spent waiting for these notes to be recomputed.  This check-point or makefile capability is built on open-input.

    open-input  &optional  name 
            &key verbose  if-does-not-exist  element-type  mix-at  mix-duration  force-recomputation
    open-output  &optional  (name  default-sound-file-name)
    close-input  &optional  (i-stream  *current-input-file*)
    close-output  &optional  (o-stream  *current-output-file*)
    reopen-output  &optional  (o-stream  default-sound-file-name)
    mix-in  file-name  begin-time  &optional  duration
    mix-sound  output-file  output-start-samp  input-file  input-start-samp  samples-to-merge
    sound-let (see page 30)
    clm-open-input name

These functions open and close input and output sound files.  Open-input and open-output take either strings or pathnames and return an IO object (see mus.lisp for a description of the innards of this structure).  Various clm functions use that object as a handle on the file.  The variable default-sound-file-name is "/zap/test.snd" at CCRMA.  It is set in next-io.lisp.

Open-input normally opens the sound file name and returns an IO structure that other clm functions can use to access the file.  If you don't give a complete file name (name without the .snd extension), open-input checks to see if there's either no .snd file or a later .cm or .clm file, and in that case, suspends the current computation, makes the sound file from the sources, then resumes the old computation, opening the (newly computed) sound file.  If you are working in sections, and keep the sections in separate files, the various layers of mixing can automatically notice when some section has changed, and update everything for you.  Similarly, if all your sound files get deleted, the whole piece can still regenerate itself in one operation.  You can avoid all this fanciness by calling clm-open-input.

Open-input's &key parameters are patterned after Lisp's load function:

    Verbose (default is nil) turns on some informational printout.
    Element-type can be nil (the default), or :sound.  In the latter case, the file passed to
        open-input is assumed to contain sound data, no matter what extension it has.  This is
        a way to override the check for out of date sound files and so on.
    If-does-not-exist can be nil or :error (the default).  In the latter case, if no sound file
        associated with name can be found or created, you get an error message.
    mix-at, mix-duration, force-recomputation Ð see mix-in below

The implicit load triggered by open-input with a non-specific file name sets *open-input-pathname* and *open-input-truename* and notices *open-input-verbose* (if t, print out informational messages).

Finally, if all you want is from open-input is the "make" action, the function mix-in provides the simplest access.  Its arguments are the file name and where to place the resulting sound file in the current output file (in seconds) and, optionally, how much of the mixed-in file to include.  If you want to avoid the fancy checkpoint stuff in open-input, or start at some arbitrary place in the input file, the next lower level is mix-sound.  Mix-sound takes sample numbers, not seconds.  Force-recomputation, if t, forces the mix to be recomputed.

DEFINSTRUMENT

Definstrument is the most common way to define an instrument in CLM.  Its syntax is almost the same as defun, but in addition it has a few options.  These fulfill two roles:  specifying instrument output language details, and requesting minor optimizations.  The options are in a state of flux; currently they are:

       language  :c, :lisp or :56k	; specify output language, defcinstrument sets it to :c
       c-file  nil		; [C: specify the instrument intermediate C file name]
       c-include-file  nil	; [C: C code to be #include'd in the intermediate file]
       c-options  "-c -O"	; [C: C compiler switches]
       exp-env  t		; [DSP:should exponential envelopes support be loaded]
       save-labels  nil	; [DSP: should debugging labels be saved]

The output language is chosen based on the kind of CLM you load Ð on any machine but the NeXT, it defaults to :c.  On the NeXT the choice is made in all.lisp when you create CLM Ð the C output easily matches the DSP in speed for most cases, so unless you have an Ariel QP board, the recommended output language is C.  The :lisp option is useful during debugging.

The syntax for these options is somewhat similar to that of with-sound.  For example, to specify that the instrument simp should use C with the "-c -O3" flags, 

   (definstrument (simp :language :c :c-options "-c -O3") (beg dur ...)

The following instruments are included as separate .ins files in the clm directory:

complete-add	add.ins	additive synthesis
addflts	addflt.ins	filters
add-sound	addsnd.ins	mix in a sound file
badd	badd.ins	fancier additive synthesis
fm-bell	bell.ins	fm bell sounds
bigbird	bigbird.ins	waveshaping	bird.clm (also bird.ins)
canter	canter.ins	fm		bag.clm (bagpipes)
cellon	cellon.ins	feedback fm
clarinet	clar.ins	physical model(?) of a clarinet
drone	drone.ins	additive synthesis	bag.clm
filter-noise	fltnoi.ins	filter with envelopes on coefficients
filter-sound	fltsnd.ins	filter a sound file
fm-insect	insect.ins	fm
fusion	fusion.ins	spectral fusion using fm
jc-reverb	jcrev.ins	an old reverberator
fm-voice	jcvoi.ins	jc's fm voice
kiprev	kiprev.ins	a fancier (temperamental) reverberator
lbj-piano	lbjPiano.ins	additive synthesis piano 
mlb-voice	mlbvoi.ins	mlb's fm (originally waveshaping) voice
fm-noise	noise.ins	noise maker
nrev	nrev.ins	a popular reverberator
pins	san.ins	spectral modelling
pluck	pluck.ins	Karplus-Strong synthesis
pqw	pqw.ins	waveshaping
pqw-vox	pqwvox.ins	waveshaping voice
resflt	resflt.ins	filters
reson	reson.ins	fm
reverberate	revsnd.ins	add reverb to sound file
fm-trumpet	trp.ins		fm trumpet
fm-violin	v.ins		fm violin	fmviolin.clm, popi.clm
vox	vox.ins	fm voice		cream.clm

The file makins.lisp exercises most of these instruments.  See also ins.lisp for a large number of examples and some tutorial explanations.  If you develop an interesting instrument that you're willing to share, please send it to me (bil@ccrma.stanford.edu).  [DSP: If you load an uncompiled instrument, you should then call compile-ins on that instrument, not compile.]

WITH-SOUND and CLM-LOAD
        ;; "With-sound: check it out!" -- Duane Kuiper, Giants broadcaster after Strawberry homer

    with-sound  &key  
	(output  default-sound-file-name)	; name of output sound file
	sndfile			; same as output
	(channels  1)			; 1, 2, and 4 are supported
	(srate  22050)			; other playable rates are 44100 and 8012
	sampling-rate 			; same as srate
	continue-old-file			; open and continue old output file
	reverb			; name of the reverberator, if any Ð the reverb
				;   is a normal clm instrument (see nrev.ins)
	reverb-data			; arguments passed to the reverberator Ð
				;   passed here as an unquoted list
	(reverb-channels 1)		; chans in temp reverb stream
	revfile			; explicit reverb file name (normally nil)
	(play  t)			; play new sound automatically?
	play-options			; args passed to user'sDAC routine
	(cleanup-first  t)			; if nil, don't try to get clean dsp state
	wait			; wait for previous computation to finish
	notehook			; form evaluated on each instrument call
	statistics			; print out various fascinating numbers
	(decay-time  1.0)			; ring time of reverb after end of piece
	output-buffer-size			; don't set unless you're a good sport
	comment			; comment placed in header (defaults to a time
				;   stamp (clm-get-default-header)
	commentary			; same as comment
	info			; non-comment header string
	type			; output sound file type
	save-body			; if t, copy the body (as a string) into the header
	verbose			; local *clm-verbose* value
	(force-recomputation nil)		; if t, force mix/with-mix calls to recompute

With-sound is a macro that wraps an "unwind-protect" around its body to make sure that everything is cleaned up properly if you happen to interrupt computation.  Here are some examples showing how to use the various options:

  (with-sound (:output "new.snd") (simp 0 1 440 .1))
  (with-sound (:srate 44100 :channels 2) ...)
  (with-sound (:play 3) ...) ;play resultant sound 3 times
  (with-sound (:comment (format nil 
	    "This version is louder: ~A" (clm-get-default-header))))
  (with-sound (:reverb jc-reverb) ...)
  (with-sound (:reverb nrev :reverb-data (:reverb-factor 1.2 :lp-coeff .95))...)

Continue-Old-file:  if t (default is nil), a previously existing file is reopened for further processing.  The default (nil) clobbers any existing file of the same name as the output file (see Output above).  By using Continue-Old-File, you can both add new stuff to an existing file, or (by subtracting) delete old stuff to any degree of selectivity.  When you erase a previous note, remember that the subtraction has to be exact Ð you have to create exactly the same note again, then subtract it.  By the same token, you can make a selected portion louder or softer by adding or subtracting a scaled version of the original.

Notehook:  a form that is evaluated each time an instrument is called (defaults to nil).  The following example prints a dot each time an instrument is called:

  (with-sound (:notehook '(princ "."))
    (simp 0 110)
    (simp 1 2))

Statistics:  currently just a flag Ð if t (default is nil), clm keeps track of a variety of interesting things and prints them out at the end of the computation.  If your favorite statistic isn't among the chosen few, send bil a complaint.

Clm-load is the same as with-sound, but its argument is the name of a file containing clm instrument calls and so on (i.e. the body of with-sound).  You can exit early from the note list with (throw :FINISH) Ð (with-sound () (load "file")) is very similar.

Instrument-let

Instrument-let is like Lisp's flet Ð it can be used to define local instruments.  For example,

(defun ins ()
  (instrument-let ((simp (beg dur freq)
	     (let* ((start (floor (* beg sampling-rate)))
		   (end (+ start (floor (* dur sampling-rate))))
		   (osc (make-oscil :frequency freq)))
	       (Run
	         (loop for i from start to end do
		 (outa i (* .25 (oscil osc)))))))
	   (toot (beg dur) 
	     (let* ((start (floor (* beg sampling-rate)))
		   (end (+ start (floor (* dur sampling-rate))))
		   (osc (make-oscil :frequency 220)))
	       (Run
	         (loop for i from start to end do
		 (outa i (* .25 (oscil osc))))))))
    (simp 0 1 660)
    (toot 3 1)))

Now, when the function Ins is called within with-sound or clm-load, it will add a note from Simp and Toot.  For a more useful example, see expsrc in ins.lisp.  (Currently in CLM, these instrument names are not purely local; the DSP code vector is stored on the symbol's property list which means, much to my surprise, that you can't have a local and a global instrument with the same name). (Also, instrument-let does not work in KCL with C output).  Don't use instrument-let within definstrument.

Sound-Let

Sound-let is a form of let* that creates temporary sound streams within with-sound.  Its syntax is like that of let and with-sound:

  (sound-let ((temp-1 () (fm-violin 0 1 440 .1))
              (temp-2 () (fm-violin 0 2 660 .1)
                         (fm-violin .125 .5 880 .1)))
    (expand-sound temp-1 0 2 0 2);temp-1's value is the name of the temp file
    (expand-sound temp-2 1 1 0 2)))

This creates two temporary files and passes them along to the subsequent calls on expand-sound.  The first list after the sound file identifier (i.e. after "temp-1" in the example) is the list of with-sound options to be passed along when creating this temporary file.  These default to :output with a unique name generated internally, and all other variables are taken from the overall (enclosing) output file.  The rest of the list is the body of the associated with-sound.

UNIT GENERATORS in CLM, not supported in RUN

    SINE-SUMMATION

        make-sine-summation  &key  (N  1)
                                                         (a . 5)
                                                         (B-ratio  1.0)
                                                         (frequency 440.0)
                                                         (initial-phase 0.0)
         sine-summation  s  &optional  (fm  0.0)

See J.A.Moorer, "Signal Processing Aspects of Computer Music" and "The Synthesis of Complex Audio Spectra by means of Discrete Summation Formulae" (Stan-M-5) Ð the basic idea is very similar to that used in the sum-of-cosines generator.  See also ins.lisp.

    ASYMMETRIC-FM 

        make-asymmetric-fm  &key  (r  1.0)
                                                       (ratio 1.0)
                                                       (frequency 440.0)
                                                       (initial-phase 0.0)
        asymmetric-fm  af  index  fm

See  Palamin and Palamin, "A Method of Generating and Controlling Asymmetrical Spectra" JAES vol 36, no 9, Sept 88, p671-685:  this is another extension of the sine-summation and sum-of-cosines approach.  See also ins.lisp.

Other Stuff of general interest

    sampling-rate	current sampling rate
    frequency-mag	scaler used to turn cycles per second into radians per sample
    *current-output-file*	current default output stream (for outa and friends)
    *current-input-file*	current default input stream (for ina and friends)
    *clm-instruments*	list of the currently loaded clm instruments (see cream.clm)
    *clm-init*		name of site-specific clm initializations, if any
    *sound-player*	user-supplied DAC funtion (see sound.lisp for details)
    *clm-array-print-length*	number of IO data buffer elements printed in trace
    *ignore-header-errors*	whether to worry about illegal sound file headers
    *fix-header-errors*	whether clm should try to make sense of illegal headers
    *grab-dac*		whether clm should ask before interrupting the DAC 
    default-sound-file-type   what kind of output sound file to write

    read-header  name
    write-header  name  &optional  header
    make-header  &key  (channels  1)  (format  snd-16-linear)   data-location
                                          (sampling-rate  22050)  (info  "    ")  (type default-sound-file-type)
    update-header  name  header  new-data-size

These header readers/writers are mostly for internal use.  The useful one is:

    edit-header  name &key  channels  datasize  datalocation  offset  dataformat  srate

This function is an endless joy on a machine with a DAC that can play at a large number of sampling rates.  Even on the NeXT, changing srate and channels can be a barrel of laughs.

    dac &optional (file  default-sound-file-name)  (report-errors t)  (start 0.0)
    dac-n  &key  (file  default-sound-file-name)  (times  1)  (worry  t)  (start  0.0)
    stop-dac
    wait-for-dac

Dac is the simple way to hear a sound file.  Its first optional argument is the file to be played (it defaults to the default sound file Ð normally "/zap/test.snd").  Its second optional argument tells it whether to worry about illegal sound file formats and similar problems.  Because NeXT's own sound file handling programs turn out files that are illegal according to NeXT's own software, we have to have a way to simply ignore errors and send arbitrary bytes to the DAC.  So, for example, (dac "bad.snd" nil) explicitly overrides the error checking.  Normally, the dac function starts playing in a background process and returns control to the lisp listener.  You can  stop the playing with stop-dac (synonyms).  You can cause lisp to wait until the playing is finished with wait-for-dac.  In the arguments to dac-n, File is the file name (a string), Times is the number of times to play the file, Worry is whether to worry about errors, and Start is where to start in the sound file.   If you have Jean Laroche's Play program on the NeXT, the function jrdac can replace dac Ð€its arguments are the file name and optionally the new srate.

    end-run &optional phrase		needed only on the DSP

End-run cleans up memory allocation associated with delay lines.  Any DSP instrument that calls delay lines (even implicitly as in all-pass) should call end-run after the run loop.  See the section on Phrasing, below, for a discussion of the phrase argument.

    volume	current volume.  Can be used with setf.  Values range between 0.0 and 1.0.
    dac-filter	current DAC output filter setting (setf-able).

The default "safety" setting for run (in the absence of any optimize declaration) is determined by *clm-safety* which defaults to 0.  A value of 1 or higher causes overflow checks to be enabled.  A value of 2 or higher causes array references to include bounds checks.  A value of 3 causes some of the unit generators to use "reals" rather than "fractions".  (Some of this is not yet implemented).  *clm-speed* defaults to 1; a value of 2 or 3 causes some address calculations to be done at load time, and triggers other such optimizations.

*clm-news* is a variable containing information on what has changed recently in the clm software.  Similarly *clm-version* is the current version identifier (a date).  If you want clm to print out instrument names and begin times as they are computed, set the variable *clm-verbose* to t.

fltdes.lisp has a bunch of filter design functions.  As an example of what is there, say we want to put a spectral envelope on a noise source.  

(definstrument filter-noise (beg dur amp &key x-coeffs)
  (let* ((st (floor (* beg sampling-rate)))
         (noi (make-randh :frequency (* .5 sampling-rate) :amplitude amp))
         (flA (make-filter :x-coeffs x-coeffs))
         (nd (+ st (floor (* sampling-rate dur)))))
    (Run
      (loop for i from st to nd do
        (outa i (filter flA (randh noi)))))))

(with-sound () 
  (filter-noise 0 1 .2 
    :x-coeffs 
      (clm::make-filter-coefficients 12 
        (clm::design-FIR-from-env 
          :order 12 
          :envelope '(0 0.0 .125 0.5 .2 0.0 .3 1.0 .5 0.0 1.0 0.0))))) 

The file files describes briefly each of the files in the clm directory;  clm-example.lisp shows one way to write notelists;  cm-clm.lisp is a brief example of using Rick Taube's Common Music to drive CLM.  There are also examples on the cm/clm directory; the cm/220 directory examples use the MusicKit, not CLM.

PHRASING

Sometimes an instrument needs to know the final state of some preceding instrument to decide its initial state (in legato for example, it sometimes helps to match phases and so on).  This state can be impossible to calculate outside the actual sound computation (it might depend on random numbers, or a phase might depend on FM), so the following functions are provided to package all this up and make it relatively easy to use.

First, an instrument that uses phrase information:

(definstrument pickup (phrase beg dur frq amp)
  ;; this instrument shows how to get the final state of
  ;; on-chip variables and pass them on to the next call.
  ;; The idea here is that by matching phases and so on
  ;; we might get a cleaner legato in some cases.
  (wait-for-phrase phrase)
  (let* ((s (or (phrase-value phrase 'S)
                (make-oscil :frequency frq)))
         (start (floor (* beg sampling-rate)))
         (end (+ start (floor (* dur sampling-rate)))))
    (Run
      (loop for i from start below end do
        (outa i (* amp (oscil s)))))
    (End-Run (phrase phrase 'S))))

and an example of running it:

    (setf phrase (make-phrase))
    (with-sound () (pickup phrase 0 1 440 .1)
                   (pickup phrase 1 1 440 .1))

The Loop in the run body uses below not to because we want to start the next instrument on the very next sample Ð if we used "loop from start to end" in both, we would have written the end sample of the first instrument twice.

The variable Phrase above is the identifier for a given phrase.  It is created with:

    make-phrase  &optional  anything

You can include anything you like in the phrase information being passed around.  Just pass it as the optional argument to make-phrase.

Once created, an instrument can wait for that phrase to be ready using

    wait-for-phrase  &rest  phrases

Normally you'd only wait for one phrase to complete, but it is possible to wait on any number of phrases.

You then get any saved variable's value with

    phrase-value  phrase  var

Phrase-value returns nil if the given variable has not yet been set (i.e. normally at the start of the phrase).  Phrase-value with a nil var argument is a reference to the "anything" that you want passed around with the phrase.

To save state in the phrase structure, call phrase as the optional argument to end-run.  The arguments to phrase are the phrase structure and the names of the variables you want saved.

    phrase  phrase  &rest vars

Outside the dsp world (i.e. outside run and end-run) there's no complication because an instrument is just a lisp function and it can return anything it likes.  Inside the dsp world, however, many dsp's can be running in parallel, simultaneous phrases may be interleaved, and we have no way in advance of knowing when or where the next portion of a given phrase will be needed, or which dsp will be free to handle it.  In addition, the definstrument function returns immediately after setting up the dsp Ð it returns long before any sound has been computed, so a subsequent definstrument call has to know how to wait its turn.

Here's another similar example Ð in this case we abut each instrument in the phrase, so none of the Pickup instruments gets a begin time. We use the nil option of phrase-value to store the end of the current instrument Ð since the variable End does not explicitly appear within the run body (the Loop is a special case), we can't use (phrase phrase 'S 'END).  If we had stared at (describe-dsp-state) long enough we might surmise that instead we could save *run-end*, but in general, user variables that don't make it into run need to be saved elsewhere (for example, the nil phrase-value slot might be an association list). Just for fun we'll play the same sort of game with the AMP variable.

(definstrument pickup (phrase dur frq)
  (wait-for-phrase phrase)
  (let* ((s (or (phrase-value phrase 'S)
                (make-oscil :frequency frq)))
         (amp (* .9 (or (phrase-value phrase 'AMP)
             .5)))
         (start (or (phrase-value phrase nil)
                     0))
         (end (+ start (floor (* dur sampling-rate)))))
    (setf (phrase-value phrase nil) end)
    (Run
      (loop for i from start below end do
        (outa i (* amp (oscil s)))))
    (End-Run (phrase phrase 'S 'AMP))))

(setf phrase (make-phrase))
(with-sound () (pickup phrase 1 440)
               (pickup phrase 1 440))

STRUCTS

Several of the instruments in ins.lisp make explicit reference to the structs defined in mus.lisp Ð the run macro knows about many of these, although a few are interpreted in somewhat odd ways.  The following structure fields should work in run more or less as they do in lisp:

OSC	(oscil):
    Osc-freq			; in radians per sample
    Osc-phase			; in radians

TBL	(table-lookup):
   Tbl-phase			; in radians
   Tbl-freq			; radians per sample
   Tbl-table			; the location of the table
   Tbl-table-size
   Tbl-internal-mag			; table size scaler to make period two pi

DLY	(delay lines):
    Dly-size			; length of delay line
    Dly-pline			; location of delay line Ð many possibilites here
    Dly-loc			; current pointer in delay line

ZDLY	(zdelay):
    Zdly-del			; location of zdelay delay generator
    Zdly-phase			; current location in delay line

CMBFLT	(comb and notch filters):
    Cmbflt-scaler
    Cmbflt-dly-unit			; location of delay line associated with comb filter

ALLPASSFLT	(all-pass filters):
    Allpassflt-feedback			; scaler on feedback term
    Allpassflt-feedforward		; scaler on feedforward term
    Allpassflt-dly-unit			; location of associated delay unit

FLT	(direct-form and associated filters):
    Flt-m			; the filter order (0 based)
    Flt-typ			; 0=direct, 1=lattice, 2=ladder
    Flt-so
    Flt-a;			 a table of coefficients Ð the "y" coeffs
    Flt-b			; ditto Ð the "x" coeffs, also known as "p"
    Flt-c			; ditto Ð used only by ladder filter
    Flt-d			; state of filter

FRMNT	(formnt):
    Frmnt-g			; gain
    Frmnt-tz			; location of two-zero filter
    Frmnt-tp			; location of two-pole filter

COSP	(sum-of-cosines):
    Cosp-phase			; radians
    Cosp-freq			; radians per sample

SMPFLT	(various simple filters Ð one-pole et al):
    Smpflt-a0 Ð these coefficients are divided by 2 on chip
    Smpflt-a1, Smpflt-a2, Smpflt-b1, Smpflt-b2, Smpflt-x1, Smpflt-y1, Smpflt-x2, Smpflt-y2

The following code fragment from addflt.ins shows one use of these fields to put envelopes on the filter coefficients:

       (dotimes (k numFilts)
         (incf outsig (ppolar (aref pps k) (* (aref scs k) insig)))
         (setf (smpflt-b1 (aref pps k)) (env (aref ffs k)))
         (setf (smpflt-b2 (aref pps k)) (env (aref rfs k))))

NOI	(randh and randi):
    Noi-phase			; radians
    Noi-freq			; radians per sample

SW	(pulse-train, square-wave, triangle-wave, sawtooth-wave):
    Sw-phase			; radians
    Sw-freq			; radians per sample

WS	(waveshape):
    Ws-tab			; location of polynomial table
    Ws-os			; location of lookup oscillator
    Ws-offset			; midpoint of table

LOCS	(locsig):
    Locs-ascl			; outa scaler Ð fractional (i.e. [-1.0,1.0))
    Locs-bscl			; outb scaler
    Locs-cscl			; outc scaler
    Locs-dscl			; outd scaler
    Locs-rscl			; reverb signal scaler
    Locs-revname			; whether reverb is on or not

(setf (locs-ascl loc) (env panning))  puts an envelope on the outa scaler.

SMP	(resample):
    Smp-sr			; current srate change
    Smp-lst			; last input value
    Smp-nxt			; next input value
    Smp-i			; current interpolation location

SR	(src):
    Sr-incr
    Sr-rd			; location of readin structure
    Sr-x			; current src location
    Sr-data			; input sequence for low pass filtering
    Sr-filt			; filter coefficients
    Sr-left
    Sr-right
    Sr-width			; sinc table size

RDIN	(readin and readin-reverse):
    Rdin-i			;current sample index
    Rdin-inc			;-1 (readin-reverse) or 1 (readin)

RBLK	(run-block):
    Rblk-siz
    Rblk-buf			; location of block data table
    Rblk-loc			;0=>trigger run-block actions
    Rblk-ctr			;count down from freq samps to 0

WT	(wave-train):
    Wt-wave			; location of table containing current wave
    Wt-wsiz
    Wt-freq			; radians per sample
    Wt-b			; location of run-block rblk structure
    Wt-phase			; radians
    Wt-internal-mag

FFTFLT	(fft-filter):
    Fftflt-env			; either the filter array or envelope
    Fftflt-siz			; true (power of 2) size of fft
    Fftflt-hop			; hop size of sliding fft's
    Fftflt-rd			; readin structure
    Fftflt-b			; run-block structure
    Fftflt-datar			; real data
    Fftflt-datai			; imaginary data
    Fftflt-half-siz

FFT-DATA	(fft and inverse-fft):
    Fft-Data-Real			;the real part of the fft data (or the sound input data)
    Fft-Data-Imaginary			;the imaginary part of the fft data
    Fft-Data-Size			;data size

SPD	(expand):
    Spd-rd, Spd-len, Spd-rmp, Spd-amp, Spd-in-spd, Spd-out-spd, Spd-cur-in, Spd-cur-out,
    Spd-s20, Spd-s50, Spd-ctr

    Spd's fields take more explanation than they're worth Ð see the code in mus.lisp for details.

In the C output case, a number of other fields are accessible, including those of the IO structure.

def-clm-struct

In addition, you can use your own structs in run if you first define them for run, as in the following example:

(def-clm-struct hi ho (silver envelope) (away array integer))
;; this replaces the lisp defstruct statement

(definstrument simp ()
  (let ((ha (make-hi :ho .1 :away 2
                     :silver (make-env :envelope '(0 0 100 1)
                                       :scaler .1 :start 0 :end 10))))
    (run
      (loop for i from 0 to 10 do
        (outa i (+ (hi-ho ha) (* .1 (hi-away ha)) (env (hi-silver ha))))))))

Def-clm-struct is a macro whose first argument is the name of the struct and the rest of the arguments are the field names you intend to access in the run block.  If the field type is not real, you should include the type as the second element of the (name type) list describing that field Ð these are clm internal types like dly or envelope.  [DSP: An array of fractional values can be passed as a table].  An array of any clm type can be passed as an array with the type of the element added as the third element of the field description: (envs array envelope):

  (def-clm-struct hi (ho array envelope) silver away)
  (definstrument simp ()
    (let ((ha (make-hi 
	:ho (make-array 1 :element-type 'envelope 
	       :initial-element 
		(make-env :envelope '(0 0 100 1) 
			 :scaler .1 :start 0 :end 10)))))
      (run
        (loop for i from 0 to 10 do
          (outa i (env (aref (hi-ho ha) 0)))))))

Bill Schottstaedt   (bil@ccrma.stanford.edu)

Appendix:  How to use CLM outside with-sound or clm-load

With-sound and clm-load are macros that expand into code similar to the following:

  (set-srate 44100)
  (open-output "/zap/test.snd" (make-header :channels 2 :sampling-rate 44100))
  (setf *reverb* (open-output "/zap/reverb.snd" (make-header :channels 1 :sampling-rate 44100)))

< insert instrument calls here >

  (close-output *reverb*)
  (open-input "/zap/reverb.snd")
  (jc-reverb 0 350 :double t)
  (close-input)
  (close-output)

When a fatal error occurs (even within with-sound or clm-load), you can use this code in the interpreter to finish the current computation.  Similarly, an instrument can be called outside with-sound and friends, or notice that it is being called in that way:

  (definstrument auto (dur)
    (let* ((outf (and (not *current-output-file*)
                      (open-output "/zap/test.snd" 
                        (make-header :channels 1 :sampling-rate 22050))))
           (os (make-oscil :frequency 440))
           (end (floor (* dur 22050))))
    (Run
     (loop for i from 0 to end do (outa i (* .1 (oscil os)))))
    (when outf
      (close-output)
      (clm-cleanup)
      (dac "/zap/test.snd"))))

Now (auto) is the same as (with-sound () (auto))

Appendix: Fast sound file mixing

The fastest way to mix sound files is with fasmix Ð it is about 5 times faster than the equivalent clm instrument add-sound.   Fasmix tries valiantly to be all things to all people, but it doesn't provide absolutely every possible mixing option.  It assumes that the files to be mixed have the same sampling rate.  The fasmix call is very similar to mix-in:

  fasmix infile 
          &key (start-time 0.0) start		; start-time in output file
               (input-file-start-time 0.0) input-file-start
	duration  
	amplitude ampA ampB ampAB ampBA ampC ampD
	amp-env)		; amp-env is the list of breakpoints

Most of the options should be pretty obvious Ð to extract channel A and scale it by .25, set ampA to .25.  Here are a bunch of examples:

  ;; say we have these two sounds:

  (with-sound (:output "/zap/1.snd") 
    (loop for i from 0 to 9 do (fm-violin i 1 (* (1+ i) 100) .1)))

  (with-sound (:output "/zap/2.snd" :channels 2) 
    (loop for i from 0 to 9 do (fm-violin i 1 (* (- 10 i) 120) .1 :degree 60)))

(with-sound ()
  (fasmix "1")			;add "1.snd" to current output
  (fasmix "1" :duration 1.0 :amplitude .5)
    ;;scale first 1 sec of "1" by .5 and mix into current output
  (fasmix "1" :amplitude .5 :amp-env '(0 0 100 1));scale and envelope sound
  (fasmix "1" :duration 1.0 :start-time 1.5 :input-file-start-time 3.0))
    ;;take section in "1.snd" from 3.0 to 4.0 and mix into output starting at 1.5

  ;; the basic fasmix calls are the same if mixing stereo to stereo:

(with-sound (:channels 2) (fasmix "2" :amp-env '(0 0 100 1)))

  ;; now mono-to-stereo:

(with-sound (:channels 2) 
  (fasmix "1") 			;add "1" into channel 1 of output
  (fasmix "1" :ampA .5 :ampB .5)	;add .5 of "1" into each channel
  (fasmix "1" :ampB 1.0 :duration 2.0 :input-file-start-time 2.0 :amp-env '(0 0 1 1 2 1 3 0)))
    ;;put envelope portion (2.0:4.0) only into channel 2 (B)

  ;; and stereo-to-mono:

(with-sound () 
    (fasmix "2")		;channel 1 of "2" added into output (i.e. extract channel 1)
    (fasmix "2" :ampB .75)		;just channel 2 (scaled) of "2" into output
    (fasmix "2" :ampA .5 :ampB .75)	;mix chanA*.5+chanB*.75 into output
    (fasmix "2" :ampA 1.0 :duration 3.0 :input-file-start-time 2.0 :amp-env '(0 0 1 1 2 1 3 0)))
			;chanA from 2.0 to 5.0, enveloped

  ;; and stereo to stereo where we want to mix the separate channels as well as scale them

(with-sound (:channels 2)
    (fasmix "2" :ampB 1.0 :ampA 1.0 :ampBA .1 :ampAB .2))
			;outA<-(inA*1.0 + inB*.2), outB<-(inA*.1 + inB*1.0)

  ;; fasmix can perform header and data type translations:

  (with-sound (:srate 8012) (fasmix "/me/cl/mulaw.snd"))  ;from mulaw to 16-bit linear
  (with-sound () (fasmix "/me/cl/aiff1.snd"))   ;from 8-bit to 16-bit linear (AIFF->NeXT)
  (with-sound () (fasmix "/me/cl/esps.snd"))    ;from ESPS to NeXT (16-bit linear)

Appendix: Header and Data Types Supported by CLM

CLM  normally writes NeXT/Sun 16-bit linear sound files.  It can also write AIFF or RIFF files,  the data always being 16-bit linear.  Read support is provided for the following headers and data types:

 *      NeXT/Sun/DEC 8-bit mulaw, 8-bit linear, 24-bit linear, 32-bit linear, 32-bit float, 
                                     8-bit alaw, 16-bit emphasized, 64-bit double
 *      AIFF 8-bit linear
 *      8SVX 8-bit linear
 *      IRCAM 16-bit linear (BICSF), EBICSF
 *      NIST-SPHERE 16-bit linear
 *      INRS, ESPS, AVR 16-bit linear, AVR 8-bit linear and unsigned
 *      RIFF (wav) 8-bit alaw, 8-bit mulaw, 8-bit unsigned, 32-bit linear
 *      VOC 8-bit signed
 *      no header (CLM will prompt for info it needs)
 *      Sound Tools 8-bit unsigned
 *      Turtle Beach SMP 16-bit linear
 *      Sound Designer II (the data file, not the resource fork)

I am willing to add almost anything to this list, except compressed data that requires block access.  See the file headers.c for all the gory details.  In with-sound, you can set the output header type with the keyword :type.

Appendix: Debugging Aids

The two main debugging aids are describe-dsp-state (for instruments running on the 56000) and describe-c-state (for instruments running in C).  These try to show the names and values of all the variables local to the currently running instrument..  Describe-c-state works only if the variable c-debug is set to t.  To play with the 56000, see the various debugging functions in lib56.lisp Ð dsp-open, dsp-close, dsp-debug, and so on.  Describe-c-state can only find the instrument data if the variable c-debug is t.  You can look at the clm compiler's intermediate code by setting c56-debug to t.

Appendix: Sources of Unwanted Noise

The major source of unwanted noise in computer music is amplitude quantization.  This means soft notes are buzzy.  If these are later used as input to a reverberator, for example, the buzziness can easily be magnified.  If the soft notes are split between channels (via locsig for example), you may end up increasing the overall noisiness.  My experience has been that anything under .003 in amplitude is asking for trouble unless it is covered up in some way.  Since reverb amounts are often less than .01, even a loud note can produce noisy input to the reverberator.  The simplest way around this in CLM is to make one run and get the reverb stream max amp (reported if :statistics is t in with-sound).  Then scale all the reverb signals up by the inverse of this (leaving some room for slop, of course).  For example, if the reverb max amp is .01, you're throwing away about 7 bits of amplitude resolution in the reverb input.  So, in this case, multiply all the locsig revscales by (say) 50, then in the reverberator, divide the reverb output by 50 before sending it out.  This only works with reverberators that use C, not the DSP, but C is much faster than the DSP in this case anyway.  See jcrev.ins for an example (the volume argument).  Similarly, if your notes are soft, send them directly out channel A or B Ð the spatial effects are going to add less to your piece than the noise will detract in this case.  And if you're making repeated iterative passes over sound files, try to keep them scaled close to 1.0 in maxamp through all the passes.  The exact maxamp is not important Ð the difference between 1.0 and .5 is one bit of resolution.

Next in importance, in my unhappy experience with headphones, speakers, and amplifiers has been that slow amplitude envelopes can cause annoying artifacts in less than perfect audio equipment.  You may hear glissandos or chirps if a soft note is slowly ramping up or down.  The function reduce-amplitude-quantization-noise in env.lisp makes the envelope jump through the initial or final section where there are too few bits, hopefully reducing the chirps (these are apparently caused by non-linearities in the audio equipment Ð I don't hear them in very good equipment).  A similar trick is to use exponential envelopes that are bowed out (a base greater than 1.0 in CLM) Ð this is the default in the MusicKit for example.   There may be some combination of problems here Ð see Robert Maher, "On the Nature of Granulation Noise in Uniform Quantization Systems", JAES vol 40 no 1/2, 1992, p12 Ð he says "The common assumption that the quantization noise is additive, white, and uncorrelated with the input signal is simply incorrect for the case of sinusoidal input".  Since the ramping portions are often sinusoidal, or nearly so, these chirps might be analyzable as FM caused by quantization.

Another source of "noise" is foldover.  This is mostly a problem with FM when the sampling rate is low Ð you'll hear inharmonic artifacts caused by components that are beyond half the sampling rate.  Then there are the obvious problems like discontinuities in the wave becoming clicks at the speaker, and clipping Ð these are easy to fix. Finally, avoid reverberating notes with sharp attacks.

The major sources of noise in audio equipment are bad ground connections and "ground loops".  In the former, either your ground connections aren't actually connected to a real ground (a common state of affairs in older U.S. houses), or the connections are flakey. In the latter, various pieces of equipment are at slightly different ground voltages due to tiny resistances in ground wires Ð this sets up a sort of screen upon which all kinds of interference can be projected, so to speak.  These loops are hard to avoid.  In my house, for example, the grounds are real Ð that is, the third plug on the power cords is actually connected to a solid ground, but I still had to connect all the drives, audio equipment, and NeXT itself to the same outlet (representing altogether about 1000 watts of worst case power consumption). The problem was that the ground wire meanders through the conduits alongside the power wires, so it becomes a serial connection of grounds along a given path Ð it's a low gauge wire, but even so, there is a slight resistance along that path, causing equipment connected to different outlets to be at a slightly different ground voltage.  Even after putting everything on the same outlet, the external drive was the source of a slight hum Ð after unplugging it, I could turn all the volume knobs up all the way and hear only a small hiss which I attribute to the unavoidable randomness in any analog equipment (Johnson noise, for example).

On the NeXT, the DACs are mounted near the monitor, so you can hear interference from that all the time. Similarly speaker cables can sometimes act as antennas Ð I think the theory here is that radio frequency interference (which can be picked up by a short wire like a speaker connection) can occur in pulses that become clicks or hiss in the speakers (audio frequencies are not picked up directly; for example a 1000 Hz electromagnetic wave is 300,000 meters long).  The simplest answer is to buy properly shielded computer equipment, and route the cables away from that equipment.  If that's not an option, it might help to use shielded cables.

			INDEX

abort-dac	31	formnt		14-15,35	readin		10,36
all-pass	16,35	frequency-mag		31	readin-reverse		10,36
amplitude-modulate	23	fft 		24,37	read-backward		10
asymmetric-fm	30	fft-filter		20-21,37	read-forward		10
clear-block	20,24	fft-window		24	read-position		10
*clm-version*	32	get-beg-end		23	reopen-output		25-26
*clm-array-print-length*	31	get-chebychev-coefficients	18,20	resample		19,36
clm-get-channels	23	*ignore-header-errors*	31	restart-env		7
clm-get-default-header	23	in-a, in-b, in-c, in-d, ina, inb	9	*reverb*		9
clm-get-duration	23	in-hz		24	revin, revout		9
clm-get-max-amp	23	instruments		3-4,27	ring-modulate		23
clm-get-samples	23	instrument-let		29	roomsig		11
clm-get-sampling-rate	23	inverse-fft		24	run-block		20,37
*clm-init*	31	ladder-filter		15-16	sampling-rate		31
*clm-instruments*	31	lattice-filter		15-16	sawtooth-wave		12-13,36
clm-load	28-29	lisp functions		4	set-srate		23
*clm-news*	32	load-synthesis-table	11	signify		18
clm-open-input	25-26	load-synthesis-table-with-phases	11	sine-summation		30
*clm-safety*	32	locsig		10-11,36	*sound-player*		31
*clm-speed*	32	make-filter-coefficients	32	sound file format translation	39-40
*clm-verbose*	32	make-fft-data-arrays	24	sound-let		30
close-input	25-26	make-header		31	square-wave		12-13,36
close-output	25-26	make-phase-quad-table	18	src		19,36
clu-reset	24	make-phrase		33	stereo		23
comb	16,35	make-waveshape-table	18	stop-dac		31
contrast-enhancement	20	mix		25	structs		35-36
convolve	21-22	mix-in		25-26 	sum-of-cosines		22,35
*current-input-file*	31	mix-sound		25-26	table-interp		12
*current-output-file*	31	mono		23	table-lookup		11,35
dac, dac-n	2,31	notch		16,35	triangle-wave		12-13,36
dac-filter	32	one-pole		13,36	two-pole		13,36
default-sound-file-name	25	one-zero		13,36	two-zero		13,36
default-sound-file-type	 31	open-input		25-26	update-header		31
definstrument	26-27	*open-input-pathname*	26	volume		32
delay	17,35	*open-input-truename*	26	wait-for-dac		31
def-clm-struct	37-38	*open-input-verbose*	26	wait-for-phrase		34
design-FIR-with-env	32	open-output		25-26	waveshape		18,36
direct-filter	15-16	oscil 		5-6,35	wave-train		20,37
divenv	8	outa, outb, outc, outd	9	with-mix		25
divseg	8	phrase		33-34	with-sound		28-29
edit-header	31	phrase-value		34	write-header		31
end-run	32	polynomial		18-19	zdelay		17,35
env	7-9	ppolar		14	zpolar		14
expand	22,37	pulse-train		12-13,36	ztap		17
fasmix	39	quad		23
filter	15-16,32,35	randh, randi		12,36
*fix-header-errors*	31	read-header		31