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; CLM will use the 56000 if it is available). 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 GCL's defentry function, CLM's instrument-let only works in GCL 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, [GCL] = only in GCL, 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.txt), 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.txt 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 Sun, 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.
(name arg1 arg2)
name(arg1,arg2);
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 GCL);(it is automatically included in the Common Music image at CCRMA), Then type
(with-sound () (fm-violin 0 1 440 .1))
(with-sound () (fm-violin 0 1 440 .1) (fm-violin 0 1 660 .1))
(with-sound (:reverb jc-reverb) (fm-violin 0 .1 440 .2))
A large number of instruments are already compiled and ready for use -- see ins.txt 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 *.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)))
(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)))))))
(with-sound () (simp 0 1 440 .1))
(definstrument name (args) setup code (run (loop for i from beg to end do run-time code )))
[DSP: If the instrument uses delay lines, it should end with (end-run) to free up the lines.]
+ / * - 1+ 1- incf decf setf setq psetf psetq = /= < > <= >= zerop plusp minusp oddp evenp max min abs mod rem gcd lcm floor ceiling round truncate signum sqrt random float ash log expt exp sin cos tan asin acos atan cosh sinh tanh asinh acosh atanh or and not null if unless when cond progn prog1 prog2 case tagbody go break error warn print princ terpri y-or-n-p yes-or-no-p block return return-from let let* loop do do* dotimes declare lambda apply funcall aref elt array-total-size array-in-bounds-p array-rank array-dimension eref nth various logical and byte manipulation functions
Generally user variables are assumed to be either integer, short-float, one of the struct types defined in mus.lisp (see Structs), or a user-defined struct that has been defined for run's benefit with def-clm-struct. 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.txt 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).
[C: If the optimize quality safety is set to be greater than 1, each unit generator is checked for plausibility before being called; this helps protect against bus errors when a null generator is called.]
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))
(oscil oscillator)
(oscil oscillator (+ vibrato glissando frequency-modulation))
make-oscil &key (frequency 440.0) ; Hz (initial-phase 0.0) ; radians ; (to convert degrees to radians multiply by .0174532925) oscil os &optional (fm-input 0.0) (pm-input 0.0)
(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)))))))))
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 env e restart-env e ; return to start of envelope
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)
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.]
To get arbitrary connecting curves between the break points, treat the output of env as input to the connecting function. Here's an example that maps the line segments into sin x^3:
(definstrument mapenv (beg dur frq amp en) (let* ((bg (floor (* beg sampling-rate))) (nd (+ bg (floor (* dur sampling-rate)))) (o (make-oscil :frequency frq)) (zv (make-env :envelope en :start-time beg :duration dur))) (run (loop for i from bg to nd do (let ((zval (env zv))) ;zval^3 is [0.0..1.0], as is sin between 0 and half-pi. (outa i (* amp (sin (* half-pi zval zval zval)) (oscil o)))))))) (with-sound () (mapenv 0 1 440 .4 '(0 0 50 1 75 0 86 .5 100 0)))
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 '(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)
env.lisp has a variety of functions that operate on envelopes. The most useful are:
env-reverse reverse an envelope env-repeat repeat an envelope env-concatenate concatenate any number of envelopes env+ add together any number of envelopes env* same but multiply env-max find max y value of envelope(s) 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
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*)
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*)
revin i revout i val
make-readin &key file start-time start (channel :A) readin rd make-reverse &key file start-time start (channel :A) readin-reverse rd
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))))
make-locsig &key (degree 0.0) (distance 1.0) (revscale 0.01) revin locsig loc i in-sig
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.
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)
load-synthesis-table synth-data table &optional (norm t) load-synthesis-table-with-phases synth-data table &optional (norm t)
'(1 .5 2 .25)
spectr.clm has a large number of steady state spectra of standard orchestral instruments, courtesy James A. Moorer. bird.clm has about 50 North American bird songs. Very large tables should be handled as sound files and read with ina and friends.
make-randh &key (frequency 440.0) ;freq at which new random numbers occur (amplitude 1.0) ;numbers are between -amplitude 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)
[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.
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 pervib) (randi ranvib))
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
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)
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)
The impulse response of a two-pole filter section is an exponentially decaying sinusoid:
Rn * cos(2*pi*(Freq/Srate)*n+Phi)
Formnt is recommended for resonators (simple bandpass filters), vocal tract or instrument cavity simulations, etc. It provides robust bandpass filtering (simple resonance) using two poles and two zeroes. Only one coefficient need be changed in order to move the filter center frequency. The filter coefficients are set as a function of desired pole-radius R (set by desired bandwidth), center frequency F, and peak gain G.
Here's a little instrument that uses formnt to filter white noise:
(definstrument simp (beg end R freq G) (let* ((amp .5) (f (make-formnt R freq G)) (noi (make-randh :frequency 5000 :amplitude .1))) (run (loop for i from beg to end do #-just-lisp (declare (optimize (safety 1))) ;catch and report overflows (outa i (* amp (formnt f (randh noi)))))))) (With-sound () (simp 0 10000 .99 1000 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 make-FIR-filter &key coeffs order 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 FIR-filter fl inp
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. FIR-filter is an optimization of direct-filter. Associated with it are three functions:
make-filter-coefficients order coeffs design-FIR-from-env &key order envelope dc make-FIR-coeffs order env
make-comb mlt length comb cflt input make-notch mlt length notch cflt input
y(n) <= x(n-length-1) + mlt * y(n-length)
See zd.ins for interpolating versions of the comb, notch, and all-pass generators (zcomb, znotch, zall-pass).
make-all-pass feedback-scaler feedforward-scaler length all-pass f input
y(n) <= feedforward-scaler*x(n-1) + x(n-length-1) + feedback-scaler*y(n-length)
make-delay length &key initial-contents initial-element delay d input tap d &optional (offset 0)
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)
(definstrument zcomb (time duration freq amp length1 length2 feedback) (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))) (zenv (make-env :envelope '(0 0 1 1) :scaler (- length2 length1) :start-time time :duration duration))) (run (loop for i from beg to end do (let* ((offset (env zenv)) (dly-val (ztap d0 offset))) (outa i (zdelay d0 (+ (* amp (pulse-train s)) (* feedback dly-val)) offset))))) (end-run)))) (with-sound () (zcomb 0 3 100 .1 20 100 .5) (zcomb 3.5 3 100 .1 100 20 .95))
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)
(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))))))
polynomial coeffs x
(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)))))))))))
make-resample &key file (srate 1.0) start-time start (channel :A) resample s &optional (sr-change 0.0)
make-src &key file (srate 1.0) (channel :A) start-time start (width 5) src s &optional (sr-change 0.0)
(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 in-samp &optional (fm-index 1.0)
make-wave-train &key wave (frequency 440.0) (initial-phase 0.0) wave-train w &optional (fm 0.0)
make-block &key size trigger run-block blk
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
(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)))
make-convolve &key filter file start-time start (channel :A) (fft-size 512) convolve ff
(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)))
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 (accuracy 1) ;how closely to follow the original expand e
make-sum-of-cosines &key (cosines 1) (frequency 440.0) (initial-phase 0.0) sum-of-cosines cs &optional (fm 0.0)
ring-modulate in1 in2 amplitude-modulate am-carrier input1 input2
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.
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)
(defcinstrument ss (beg dur freq amp &optional (N 1) (a .5) (B-ratio 1.0)) (let* ((st (floor (* sampling-rate beg))) (nd (+ st (floor (* sampling-rate dur)))) (sgen (make-sine-summation :n N :a a :b-ratio B-ratio :frequency freq))) (run (loop for i from st to nd do (outa i (* amp (sine-summation sgen)))))))
make-asymmetric-fm &key (r 1.0) (ratio 1.0) (frequency 440.0) (initial-phase 0.0) asymmetric-fm af index fm
(defcinstrument asy (beg dur freq amp index &optional (r 1.0) (ratio 1.0)) (let* ((st (floor (* beg sampling-rate))) (nd (+ st (floor (* dur sampling-rate)))) (asyf (make-asymmetric-fm :r r :ratio ratio :frequency freq))) (run (loop for i from st to nd do (outa i (* amp (asymmetric-fm asyf index 0.0)))))))
stereo &optional (o-stream *current-output-file*) ;2 channels mono &optional (o-stream *current-output-file*) ;1 channel quad &optional (o-stream *current-output-file*) ;4 channels
in-Hz converts its argument 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.
(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)))))))
language :c, :lisp or :56k ; specify output language -- defcinstrument for :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] parallel nil ; [C: this is a "sample-synchronous" instrument -- defpinstrument]
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 ...)
Defpinstrument is the same as defcinstrument except that in addition to producing C output the instrument is handled by a special scheduler that runs it one sample at a time, in parallel with all other p-instruments that want to run on that sample. This kind of scheduling is useful when instruments share global data that they are modifying as they run. P-instruments can be mixed freely with "normal" instruments. The latter run to completion when they are called, while the currently running p-instruments wait in the background. There are currently a few limitations associated with p-instruments: local sound output files and phrase-value are not supported, p-instruments are unlikely to behave correctly within mix and with-mix, and calls on these instruments must be ordered by begin-time.
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 cnvflt-reverb cnv.ins convolution-based reverb drone drone.ins additive synthesis bag.clm filter-noise fltnoi.ins filter with envelopes on coefficients filter-sound fltsnd.ins filter a sound file stereo-flute flute.ins physical model of a flute (Nicky Hind) 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 track-rms rms.ins rms envelope of sound file (Michael Edwards) singer singer.ins Perry Cook's vocal tract physical model fm-trumpet trp.ins fm trumpet fm-violin v.ins fm violin fmviolin.clm, popi.clm vox vox.ins fm voice cream.clm zc, zn zd.ins examples of zdelay (zcomb, znotch, zall-pass)The file makins.lisp exercises most of these instruments. See also ins.txt 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).
with-sound &key (output default-sound-file-name) ; name of output sound file sndfile ; same as output (channels 1) ; 1, 2, and 4 are supported (srate 22050) ; other playable rates are 44100 and 8012 sampling-rate ; same as srate continue-old-file ; open and continue old output file reverb ; name of the reverberator, if any -- the reverb ; is a normal clm instrument (see nrev.ins) reverb-data ; arguments passed to the reverberator -- ; passed here as an unquoted list (reverb-channels 1) ; chans in temp reverb stream revfile ; explicit reverb file name (normally nil) (play t) ; play new sound automatically? play-options ; args passed to user'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 data-format ; output sound data format (defaults to snd-16-linear) save-body ; if t, copy the body (as a string) into the header save-stats ; (NeXT sound file output only) t=save stats in header scaled-to ; if a number, scale results to have that max amp verbose ; local *clm-verbose* value (force-recomputation nil) ; if t, force mix/with-mix calls to recompute
(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))...)
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))
Data-format: CLM can write output sound data in any of the following formats: snd-16-linear (the default, and with snd-8-mulaw, the only playable data format on the NeXT), snd-8-mulaw, snd-8-alaw, snd-8-linear, snd-8-unsigned (for old Macs), snd-32-float, snd-32-linear, snd-24-linear, and snd-64-double. The most useful of these is snd-32-linear; if you aren't sure what the output max amp will be, write it out as snd-32-linear and get the accurate (possibly greater than 1.0) max amp, then scale that back while transforming to snd-16-linear to hear the result. The easiest way to do this is to use the scaled-to argument in with-sound -- if scaled-to has a value (like .9), with-sound automatically writes its results first to a 32-bit linear intermediate file, then scales that file by whatever amount is needed to end up at the requested max amp.
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.
(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)))
(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)))
Mix file begin &body body With-mix options file begin &body body
(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))
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 clm-open-input name
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 (the 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. As to mix-at, mix-duration, and 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.
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 clm-reset closes files and the DSP, and cleans up internal state get-beg-end start duration convert Start and Duration from seconds to 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. 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 File (IO struct). clm-get-samples file Returns the number of full samples in the file 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))))
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 edit-header name &key channels datasize datalocation offset dataformat srate
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
end-run &optional phrase
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).
*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 (make-filter-coefficients 12 (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)))))
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))))
(setf phrase (make-phrase)) (with-sound () (pickup phrase 0 1 440 .1) (pickup phrase 1 1 440 .1))
make-phrase &optional anything
wait-for-phrase &rest phrases
phrase-value phrase var
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
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))
(defcinstrument p (beg dur frq amp) (let* ((s (make-oscil :frequency frq)) (start (floor (* beg sampling-rate))) (end (+ start (floor (* dur sampling-rate)))) (hi 0.0)) (run* (amp hi) (loop for i from start below end do (incf hi .001) (outa i (* amp (oscil s))))) (print (format nil "~A ~A" hi amp))))
OSC (oscil): osc-freq ; in radians per sample osc-phase ; in radians TBL (table-lookup): tbl-phase ; table lookup position tbl-freq ; table locations per sample (freq=(tbl-size*Hz-freq)/srate) 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 smp-chn ; channel 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) rdin-chn ; channel 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 ; cycles per second wt-b ; location of run-block rblk structure wt-phase ; table lookup position 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 CONV (convolve): conv-siz ; true (power of 2) size of fft conv-hop ; hop size of sliding fft's conv-rd ; readin structure conv-b ; run-block structure conv-datar ; real data conv-datai ; imaginary data conv-half-siz, conv-pow, conv-filti, conv-filtr 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 ; see expsrc.ins for info ASYMFM (asymmetric-fm): asymfm-phase, asymfm-freq, asymfm-r, asymfm-ratio, asymfm-sinr, asymfm-cosr SSS (sine-summation): sss-phase, sss-freq, sss-a, sss-an, sss-a2, sss-b, sss-n IO (file handlers): IO-fil, IO-nam, IO-dat-a/b/c/d, IO-beg, IO-end, IO-hdr, IO-hdr-end, IO-data-end IO-dir, IO-open-index, IO-siz, IO-data-start
(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 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)))))))
(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)
(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"))))
A C instrument need not perform sound output, so it can be called at any time. See rms.ins for an example.
fasmix infile &key (start-time 0.0) start ; start-time in output file input-file-start-time input-file-start duration srate coeffs amplitude amp-env ampAA ampAB ampAC ampAD ampBA ampBB ampBC ampBD ampCA ampCB ampCC ampCD ampDA ampDB ampDC ampDD envAA envAB envAC envAD envBA envBB envBC envBD envCA envCB envCC envCD envDA envDB envDC envDD)
;; 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" :ampAA .5 :ampAB .5) ;add .5 of "1" into each channel (fasmix "1" :ampAB 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" :ampBA .75) ;just channel 2 (scaled) of "2" into output (fasmix "2" :ampAA .5 :ampB .75) ;mix chanA*.5+chanB*.75 into output (fasmix "2" :ampAA 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" :ampBB 1.0 :ampAA 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 (:data-format snd-32-float) (fasmix "/me/cl/aiff1.snd")) ;from 8-bit to 32-bit floats (AIFF->NeXT) (with-sound (:type AIFF-sound-file) (fasmix "/me/cl/esps.snd")) ;from ESPS to AIFF (16-bit linear) ;; and sampling rate conversion with an FIR filter: (with-sound () (fasmix "2" :srate 2 :coeffs (make-FIR-coeffs 12 '(0 0 .1 1.0 .2 0.0 1.0 0.0)))) ;; the envelope of make-FIR-coeffs is the desired frequency response approximated ;; with line segments. If you know what you want, you can pass it directly: (with-sound () (fasmix "2" :srate -1.2 :coeffs '(.25 .5 .25)))
* 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)
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 connections and "ground loops". In the latter case, 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, and hum can apparently be caused by stray inductance from transformers. The simplest answer is to buy properly shielded computer equipment, route the wires away from that equipment, and use the shortest wires possible. If that's not an option, I'm told that it helps to use shielded cables. If there's still interference, you might be able to go to "balanced" connections, but this normally means investing in pro-audio equipment.
c-open-input-file name => integer file descriptor (fd below) c-open-output-file name => fd c-close fd clm-seek-bytes fd n => go to byte n in file fd clm-seek-floats fd n => got to float n in file fd (assuming 4 byte floats) clm-read-floats fd n arr => read next n floats in fd into (lisp) array arr clm-read-ints fd n arr => same for integers (longs) clm-write-floats fd n arr => write n floats from arr[0] into fd
all-pass divseg ppolar amplitude-modulate dlocsig pulse-train array-interp dot-product quad asymmetric-fm edit-header randh,randi c56-debug end-run read-backward c-close env read-direction c-debug expand read-forward channel fasmix read-header clear-block fft readin *clm-version* fft-filter readin-reverse *clm-array-print-length* fft-window read-position clm-get-channels filter reopen-output clm-get-default-header *fix-header-errors* resample clm-get-duration formnt restart-env clm-get-max-amp frequency *reverb* clm-get-samples frequency-mag revin,revout clm-get-sampling-rate get-beg-end ring-modulate *clm-init* get-chebychev-coefficients roomsig *clm-instruments* *ignore-header-errors* run-block clm-load in-a,in-b,in-c,in-d,ina,inb safety optimization *clm-news* in-hz sampling-rate clm-open-input jrdac sawtooth-wave clm-print instruments set-srate clm-read-ints instrument-let signify clm-read-floats inverse-fft sine-summation clm-reset ladder-filter size-of *clm-safety* lattice-filter *sound-player* clm-seek-bytes lisp functions file formats clm-seek-floats load-synthesis-table sound-let *clm-verbose* load-synthesis-table-with-phases square-wave clm-write-floats locsig src close-input make-filter-coefficients stereo close-output make-fft-data-arrays stop-dac comb make-header structs contrast-enhancement make-phase-quad-table sum-of-cosines convolve make-phrase table-interp c-open-input-file make-waveshape-table table-lookup c-open-output-file mix triangle-wave *current-input-file* mix-in two-pole *current-output-file* mix-sound two-zero dac,dac-n mono update-header dac-filter notch volume data one-pole wait-for-dac debugging one-zero wait-for-phrase default-sound-file-name open-input waveshape default-sound-file-type *open-input-pathname* wave-train definstrument *open-input-truename* with-mix defcinstrument *open-input-verbose* with-sound defpinstrument open-output write-header delay oscil zall-pass def-clm-struct outa,outb,outc,outd zcomb describe-ins-state phase zdelay design-FIR-from-env phrase znotch direct-filter phrase-value zpolar divenv polynomial ztap FIR-filter run*