ftp.nice.ch/pub/next/unix/music/clm.d.tar.gz#/clm.txt

This is clm.txt in view mode; [Download] [Up]

This version of the clm documentation omits all the graphics and
font-related stuff of the original (clm.wn, clm.rtf).


                                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
	 	  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			42



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  nth  eref
    array-rank array-dimension

    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"

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
  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-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 an 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 mostly 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  &key  
        ;; "With-sound: check it out!" -- Duane Kuiper, Giants broadcaster after Strawberry homer
	(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's DAC 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, I'm told that it helps to use shielded cables.


These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.