Scheme, Ruby, and Forth Functions included with Snd


related documentation: snd.html extsnd.html grfsnd.html sndclm.html sndlib.html libxm.html fm.html index.html

This file contains notes on the Scheme, Ruby, and Forth files included with Snd. To use any of these files, load them:

  Scheme: (load "dsp.scm") or (load-from-path "dsp.scm")
  Ruby:   load "dsp.rb"
  Forth:  "dsp.fs" file-eval

To start Snd with the file already loaded, snd -l v.scm, or put the load statement in your initialization file. For help with Forth and Snd/CLM, see the Forth documentation section "Snd, CLM, and Fth".


Contents
analog-filter standard IIR filters (Butterworth, Chebyshev, Bessel, Elliptic)
animals a bunch of animals
autosave auto-save (edit backup) support
bess FM demo
bird North-American birds
clean noise reduction
clm-ins, clm23 various CLM instruments
debug debugging aids
dlocsig moving sounds (Michael Scholz)
draw graphics additions
dsp various DSP-related procedures
edit123.scm, snd_conffile.scm, snd_frg.scm .snd examples (Tom Roth, Kjetil S. Matheussen, Olivier Doare)
env envelope functions
enved envelope editor
examp many examples
extensions various generally useful Snd extensions
fade frequency-domain cross-fades
frame frames, vcts, sound-data objects
freeverb a reverb
generators.scm a bunch of generators
grani CLM's grani (Fernando Lopez-Lezcano) translated by Mike Scholz
heart use Snd with non-sound (arbitrary range) data
hooks functions related to hooks
index snd-help extension
inf-snd.el, DotEmacs Emacs subjob support (Michael Scholz, Fernando Lopez-Lezcano)
jcrev John Chowning's ancient reverb
ladspa.scm, ladspa-help.scm, gui.scm Kjetil S. Matheussen's LADSPA GUI-builder and previewer.
maraca Perry Cook's maraca physical model
marks functions related to marks
maxf Max Mathews resonator
menus additional menus
mix functions related to mixes
mixer functions related to linear algebra
moog Moog filter
musglyphs Music notation symbols (from CMN)
nb Popup File info etc
noise noise maker
numerics various numerical functions
oscope an oscilloscope/spectrum analysis dialog
peak-env peak envelope support
peak-phases phases for the unpulse-train
piano piano physical model
play play-related functions
poly polynomial-related stuff
popup, gtk-popup Popup menu specializations
prc95 Perry Cook's physical model examples
pvoc phase-vocoder
rgb color names
rt-examples and friends hard real-time support
rtio real-time stuff
rubber rubber-sound
selection functions acting on the current selection
singer Perry Cook's vocal-tract physical model
snd4|5|6|7|8|9.scm Backwards compatibility
snddiff sound difference detection
snd-gl OpenGL examples (gl.c)
snd-motif, snd-gtk, snd-xm Motif/Gtk module (xm.c, xg.c)
snd-test Snd regression tests
sndwarp Bret Battey's sndwarp instrument
spectr instrument steady state spectra
stochastic Bill Sack's dynamic stochastic synthesis
strad string physical model (from CLM)
v fm-violin
ws with-sound
zip the zipper (the anti-cross-fader)


analog-filter

make-butterworth-lowpass (order fcut)
make-butterworth-highpass (order fcut)
make-butterworth-bandpass (order flo fhi)
make-butterworth-bandstop (order flo fhi)

make-chebyshev-lowpass (order fcut (ripple-dB 1.0))
make-chebyshev-highpass (order fcut (ripple-dB 1.0))
make-chebyshev-bandpass (order flo fhi (ripple-dB 1.0))
make-chebyshev-bandstop (order flo fhi (ripple-dB 1.0))

make-inverse-chebyshev-lowpass (order fcut (loss-dB 60.0))
make-inverse-chebyshev-highpass (order fcut (loss-dB 60.0))
make-inverse-chebyshev-bandpass (order flo fhi (loss-dB 60.0))
make-inverse-chebyshev-bandstop (order flo fhi (loss-dB 60.0))

make-bessel-lowpass (order fcut)
make-bessel-highpass (order fcut)
make-bessel-bandpass (order flo fh)
make-bessel-bandstop (order flo fh)

make-elliptic-lowpass (order fcut (ripple-dB 1.0) (loss-dB 60.0))
make-elliptic-highpass (order fcut (ripple-dB 1.0) (loss-dB 60.0))
make-elliptic-bandpass (order flo fhi (ripple-dB 1.0) (loss-dB 60.0))
make-elliptic-bandstop (order flo fhi (ripple-dB 1.0) (loss-dB 60.0))

    ;; fcut = cutoff frequency in terms of srate = 1.0, 
    ;; flo = low freq of band, fhi = high freq of band

analog-filter.scm has the usual IIR filters: Butterworth, Chebyshev, inverse Chebyshev, Bessel, and Elliptic filters in lowpass, highpass, bandpass, and bandstop versions. Each of the lowpass and highpass "make" functions returns a filter generator, whereas the bandstop and bandpass make functions return a function of one argument, the current input (the filter generators are built-in in these cases). The filter order should be an even number; very high orders can cause numerical disaster! If you want to push these guys, be sure to build Snd with the --with-doubles configuration switch. The elliptic filters depend on GSL, so you'll also need GSL (Snd's configure script includes it by default, if possible).

    (let* ((flt (make-elliptic-lowpass 8 .1))) ; 8th order elliptic with cutoff at .1 * srate
      (map-channel flt))                       ; flt is a clm filter generator

One quick way to see the frequency response of your filter is to create a sound that sweeps a sinewave upward in frequency, run it through the filter, then view the entire sound, treating the x axis as frequency in terms of srate = 1.0 (for convenience):

(define (filter-sweep flt chan)
  (let ((phase 0.0)
	(freq 0.0)
	(incr (/ (* 2 pi) 44100.0))
        (samps (seconds->samples 0.5)))
    (do ((i 0 (1+ i)))
	((= i samps))
      (let ((sval (* .8 (sin phase))))
	(set! phase (+ phase freq)) 
	(set! freq (+ freq incr))
	(out-any i (flt sval) chan)))))

(with-sound (:channels 5 :output "test.snd")
  (filter-sweep (make-butterworth-lowpass 8 .1) 0)
  (filter-sweep (make-bessel-lowpass 8 .1) 1)
  (filter-sweep (make-chebyshev-lowpass 8 .1) 2)
  (filter-sweep (make-inverse-chebyshev-lowpass 8 .1) 3)
  (filter-sweep (make-elliptic-lowpass 8 .1) 4))
IIR filters, order=8, low cutoff at .1 (4410Hz), high cutoff at .3 (13230Hz)
iir filters
see also: dsp examp moog maxf prc95 graphEq clm

animals

People paint birds; why not bird songs? Check out a Hermit Thrush song down 2 or 3 octaves and slowed down as well (via granular synthesis, for example) — incredibly beautiful 2-part microtonal counterpoint. animals.scm contains several synthesized animal sounds: frogs, birds, insects, and one mammal. To hear them all, (calling-all-animals).

How to Paint a Bird Song

Back in 1980, I wanted some bird songs for "Colony", but my stabs at a fake bird song were completely unconvincing. So I went to my battered bird book (Robbins, Bruun, Zim, Singer "Birds of North America" Golden Press, NY 1966) which had sonograms of lots of bird songs. Unfortunately, the graphs were so tiny that I could barely read them:

Lincoln's Sparrow, approximately original size Lincoln's Sparrow approximately original size,
but blurrier due to incredibly bad scanner software.

Graphs like this became bird.scm. It surprised me that the synthetic song could sound good even with just a sinewave and a couple sketchy envelopes. But squawks and screeches were harder. 27 years later, I tackled animal sounds again, but now using Snd and some extremely high quality recordings, mainly from Cornell. It's not that hard to match some animal sounds; perhaps someone would like to see the steps I took to match a Hairy Woodpecker call (hairy-woodpecker in animals.scm).

Open the Hairy Woodpecker. Find a squawk that seems within reach, and select it.

selecting a squawk

Zoom onto the first channel (the vertical slider on the right — we don't care about stereo here), and center the squawk in the time domain window (C-x v). Get the selection duration in seconds. Start the envelope editor dialog (under the Edit menu). Choose "selection" and "wave" in that dialog.

zoom in

Since the goal is a CLM instrument that synthesizes this sound, get a "blank bird", and fill in the duration.

(definstrument (hairy-woodpecker beg amp)
  (let* ((start (seconds->samples beg))
	 (dur 0.08)                                      ; filled in from the selection duration
	 (stop (+ start (seconds->samples dur)))
	 (ampf (make-env '                               ; left blank for the moment
			 :duration dur :scaler amp))
	 (gen1 (make-oscil))
	 (frqf (make-env '                               ; ditto
			 :duration dur :scaler (hz->radians 1.0))))
   (run
     (lambda ()
       (do ((i start (1+ i)))
	   ((= i stop))
	 (outa i (* (env ampf)                           ; just a first guess at the synthesis algorithm
		    (oscil gen1 (env frqf)))))))))

Now to get an amplitude envelope, set the y axis limits to be from 0.0 to the selection maxamp:

    (set! (y-bounds (selected-sound) (selected-channel)) (list 0 (selection-maxamp)))

I have this action bound to the "m" key in my ~/.snd initialization file. The change is reflected in the envelope editor. We can draw the amplitude envelope simply by approximating the shape. Call it "hairy-amp".

get amp env

Now go to the listener and get the value of hairy-amp as a list of breakpoints. I usually use this function to get the breakpoints:

>(define (clean-string e)
  (string-concatenate (append (list "(")
			      (map (lambda (n) (format #f "~,3F " n)) e)
			      (list ")"))))
#<unspecified>
>(clean-string hairy-amp)
"(0.000 0.000 0.099 0.188 0.152 0.148 0.211 0.558 0.242 0.267 0.278 0.519 0.434 0.472 0.527 0.543 0.612 0.479 
  0.792 0.941 0.831 0.523 0.854 1.000 0.913 0.422 0.927 0.200 0.946 0.430 0.971 0.304 1.000 0.000 )"

Plug this list into the bird instrument (ampf). Now switch to the FFT view (click "f" and "w"), open the FFT dialog (Options:Transform), choose Blackman10 window, sonogram, 512. In the color dialog, choose the "jet" colormap. In the envelope editor, switch from "amp" to "flt", and resize the dialog window so that its graph fits the displayed spectrum. For fast moving sounds, it's important to align the amp and freq envelopes exactly, but the FFT delays or blurs stuff, so mess with the graph placement until the amplitude envelope and the spectrum match (bright spots at loud spots and so on). I bind the arrow keys to precision movements for this reason (in my ~/.snd file, see move-one-pixel).

preapring to get freq env

Reset the envelope editor (to erase the amp envelope), and press "flt" and "wave" again. Now zoom in to the main spectral component (drag the FFT y axis up), and trace out the frequency curve in the envelope editor. Call it hairy-freq, and get the list of breakpoints as before in the listener. Plug that into the bird instrument. The "scaler" for the frequency envelope is the top of the FFT graph in Hz; it is 10KHz in this case.

get freq env
(definstrument (hairy-woodpecker beg amp)
  (let* ((start (seconds->samples beg))
	 (dur 0.08)
	 (stop (+ start (seconds->samples dur)))
	 (ampf (make-env '(0.000 0.000 0.099 0.188 0.152 0.148 0.211 0.558 0.242 0.267 0.278 0.519 0.434 0.472 
			   0.527 0.543 0.612 0.479 0.792 0.941 0.831 0.523 0.854 1.000 0.913 0.422 0.927 0.200 
			   0.946 0.430 0.971 0.304 1.000 0.000 )
			 :duration dur :scaler amp))
	 (frqf (make-env '(0.000 0.180 0.056 0.213 0.135 0.241 0.167 0.305 0.191 0.396 0.212 0.402 0.242 0.485 
			   0.288 0.506 0.390 0.524 0.509 0.530 0.637 0.537 0.732 0.530 0.770 0.503 0.808 0.503 
			   0.826 0.427 0.848 0.366 0.889 0.345 0.913 0.232 1.000 0.198)
			 :duration dur :scaler (hz->radians 10000.0)))
	 (gen1 (make-oscil)))
   (run
     (lambda ()
       (do ((i start (1+ i)))
	   ((= i stop))
	 (outa i (* (env ampf)
		    (oscil gen1 (env frqf)))))))))

This squawk has more than one component (it is not just a sine wave), and the components follow more or less the same amplitude envelope (so we can use polywave). Go to the "single transform" view (in the Transform dialog), make the FFT size bigger, move the time domain window into about the middle of the call, and get some estimate of the number of components and their relative amplitudes (concentrating for now on the steady state). Change the "make-oscil" to "make-polywave" and give some first stab at the steady-state spectrum:

   ...
  (gen1 (make-polywave :partials (list 1 .9  2 .1  3 .01)))
  ...
  (polywave gen1 (env frqf))
  ...

Load ws.scm, load the current woodpecker code, and listen to the squawk: (with-sound (:play #t) (hairy-woodpecker 0.0 0.5)). Not terrible. If it's cut off during playback, add a dummy silent call to the end: (with-sound (:play #t) (hairy-woodpecker 0.0 0.5) (hairy-woodpecker 0.5 0.0)). We're happy at this stage if it's in the right ballpark.

first take

The attack and decay sections need work. Returning to either FFT view, it's clear there's a set of components moving together at half the steady state frequency, so add another polywave with its own amplitude envelope:

(definstrument (hairy-woodpecker beg amp)
  (let* ((start (seconds->samples beg))
	 (dur 0.08)
	 (stop (+ start (seconds->samples dur)))
	 (ampf (make-env '(0.000 0.000 0.099 0.188 0.152 0.148 0.211 0.558 0.242 0.267 0.278 0.519 0.434 0.472 
			   0.527 0.543 0.612 0.479 0.792 0.941 0.831 0.523 0.854 1.000 0.913 0.422 0.927 0.200 
			   0.946 0.430 0.971 0.304 1.000 0.000 )
			 :duration dur :scaler amp))
	 (frqf (make-env '(0.000 0.180 0.056 0.213 0.135 0.241 0.167 0.305 0.191 0.396 0.212 0.402 0.242 0.485 
			   0.288 0.506 0.390 0.524 0.509 0.530 0.637 0.537 0.732 0.530 0.770 0.503 0.808 0.503 
			   0.826 0.427 0.848 0.366 0.889 0.345 0.913 0.232 1.000 0.198)
			 :duration dur :scaler (hz->radians 10000.0)))
	 (gen1 (make-polywave :partials (list 1 .9  2 .09  3 .01)))
	 (gen2 (make-polywave :partials (list 1 .2  2 .1  3 .1  4 .1  5 .1  6 .05  7 .01))) ; attack and decay
	 (ampf2 (make-env '(0 1  .3 1  .4 0 .75 0 .8 1  1 1) :duration dur :scaler 1.0)))      ; its amplitude
    (run
     (lambda ()
       (do ((i start (1+ i)))
	   ((= i stop))
	 (let ((frq (env frqf)))
	   (outa i (* (env ampf)
		      (+ (polywave gen1 frq)
			 (* (env ampf2)
			    (polywave gen2 (* 0.5 frq))))))))))))

Now patience is the key. Use the speed control to slow playback down by an octave or two. (Perhaps the frequency envelope should end at a higher point?) Keep tweaking the envelopes and spectral amplitudes until it sounds right! Total elapsed time? Two or three hours probably.

the end

animals.scm has all the functions, key bindings, and dialog variable settings mentioned here. They can save you a ton of time.

see also: birds

autosave

auto-save ()
cancel-auto-save ()

The auto-save code sets up a background process that checks periodically for unsaved edits, and if any are found it saves them in a temporary file (the name is the base file name enclosed in "#...#" and placed in the temp-dir directory). The time between checks is set by the variable auto-save-interval which defaults to 60.0 seconds. To start auto-saving, (load "autosave.scm"). Thereafter (cancel-auto-save) stops autosaving, and (auto-save) restarts it.


bess

bess.scm is a Scheme file that can be run either as a Guile script (independent of Snd) or loaded into Snd. As a Guile script, it loads sndlib and xmlib, and creates its own application windows. In Snd, it creates a new dialog. In either case, it opens the DAC, puts up a bunch of scale widgets, and starts two CLM oscils doing frequency modulation in semi-real-time (how "real-time" it is depends on your audio setup). Michael Scholz has contributed a Ruby translation of this with many improvements: bess.rb. To use bess.scm as a script, you may need to make it executable: chmod 777 bess.scm. If you get "command not found", you need to edit the path to guile in the source.

fm dialog

   ;; bess opens the DAC and continuously sends the following:
   (* amp 
      (oscil carosc 
        (+ (hz->radians frequency)
           (* index (oscil modosc 
                      (hz->radians (* ratio frequency)))))))

bess1.scm and bess1.rb are scripts (independent of Snd), similar to bess.scm and bess.rb, that give you real-time GUI-based control over the fm-violin while it cycles around in a simple compositional algorithm. Both were written by Michael Scholz, based on CLM's bess5.cl and rt.lisp.

see also: fm


bird

bird (start dur frequency freqskew amplitude freq-envelope amp-envelope)
bigbird (start dur frequency freqskew amplitude freq-envelope amp-envelope partials)
one-bird (beg maxdur func birdname)
make-birds (:optional (output-file "test.snd"))

bird.scm is a translation of the Sambox/CLM bird songs. The two instruments set up a simple sine wave (bird) and simple waveshaping synthesis (bigbird). Use a low-pass filter for distance effects (a bird song sounds really silly reverberated). All the real information is in the amplitude and frequency envelopes. These were transcribed from sonograms found in some bird guides and articles from the Cornell Ornithology Lab. Many of these birds were used in "Colony". To hear all the birds, (make-birds). This writes the sequence out as "test.snd" using with-sound. Waveshaping is described in Le Brun, "Digital Waveshaping Synthesis", JAES 1979 April, vol 27, no 4, p250. The lines

   ...
   (coeffs (partials->polynomial (normalize-partials partials)))
   ...
	     (polynomial coeffs
			 (oscil os (env gls-env))))))
			 

setup and run the waveshaping synthesis (in this case it's just a fast additive synthesis). partials->polynomial calculates the Chebyshev polynomial coefficients given the desired spectrum; the spectrum then results from driving that polynomial with an oscillator. Besides the bird guides, there are now numerous recordings of birds that can be turned into sonograms and transcribed as envelopes. sndclm.html has the code for the bird instrument in several languages.

see also: animals

clean

This file provides a set of noise reduction functions packaged up in:

clean-channel (:optional snd chn)
clean-sound (:optional snd)

clean-channel tries to fix up clicks, pops, hum, DC offset, clipped portions, and hiss using a variety of functions from dsp.scm. The final low-pass filter is relatively conservative (that is, it's not a very intense filter), so you may want to run another filter over the data after calling clean-channel.


A Noisy Story

There is no built-in noise reduction function in Snd. I believe the most common such function is some variant of Perry Cook's Scrubber program (see anoi in clm-ins.scm or fft-squelch in examp.scm). Secondary tricks involve smoothing functions similar to smooth-channel, and enveloping to silence stuff between tracks, and so on. clean-channel came about when I blithely offered to clean up some recorded telephone conversations. The first step was to find the clipped locations (where the conversation was accidentally over-recorded). I did this first because there were places in the recordings where the DC offset was huge, causing clipping in a signal that would otherwise have been safe. I hoped to reconstruct the signal at the clipped points, but these would be hard to find once the DC was removed. A quick check:

  (count-matches (lambda (y) (or (> y .9999) (< y -.9999))))

returned 5437 (in 18 minutes of sound). That seemed high, and I thought "maybe those are just one sample clicks that can easily be smoothed over", so

(define* (count-clips :optional snd chn)
  (let ((y0 0.0))
    (count-matches 
     (lambda (y) (let ((val (and (or (> y0 .9999) (< y0 -.9999))
				 (or (> y .9999) (< y -.9999)))))
		   (set! y0 y)
		   val))
     0 snd chn)))

But this returned 4768! I made a list of clipped portions (this function has at least one bug, but I plowed past it — no time for perfection...):

(define* (list-clips :optional snd chn)
  (let* ((max-clips (count-clips snd chn))
	 (clip-data (make-vector (* 2 max-clips) 0))
	 (clip-ctr 0)
	 (clip-beg 0)
	 (clip-end 0)
	 (clip-max-len 0)
	 (in-clip #f)
	 (samp 0))
    (scan-channel
     (lambda (y)
       (if (or (> y .9999) (< y -.9999))
	   (if in-clip
	       (set! clip-end samp)
	       (begin
		 (set! in-clip #t)
		 (set! clip-beg samp)
		 (set! clip-end samp)))
	   (if in-clip
	       (begin
		 (set! in-clip #f)
		 (vector-set! clip-data clip-ctr clip-beg)
		 (vector-set! clip-data (1+ clip-ctr) clip-end)
		 (set! clip-max-len (max clip-max-len (1+ (- clip-end clip-beg))))
		 (set! clip-ctr (+ clip-ctr 2)))))
       (set! samp (1+ samp))
       #f)) ; make sure scan doesn't quit prematurely
    (list clip-ctr clip-max-len clip-data)))

which returned a vector of 669 clipped portions, the worst being 42 samples long! I saved that data in a separate file, just in case of disaster:

    (with-output-to-file "clips" (display (list-clips)))

I decided to try to reconstruct the clipped portions before filtering them. This produced sample values outside -1.0 to 1.0, so I reset the graph y bounds:

    (set! (y-bounds) (list -1.5 1.5))

Now to conjure up a plausible sine wave between the clip begin and end points. (This is also "just-good-enough" software).

(if (not (defined? 'pi)) (define pi 3.141592653589793))

(define (fix-clip clip-beg-1 clip-end-1)
  (if (> clip-end-1 clip-beg-1)
      (let* ((dur (1+ (- clip-end-1 clip-beg-1)))
	     (samps (channel->vct (- clip-beg-1 4) (+ dur 9)))
	     (clip-beg 3)
	     (clip-end (+ dur 4)))
	(let ((samp0 (vct-ref samps clip-beg))
	      (samp1 (vct-ref samps clip-end)))
	  (if (or (> samp0 .99) (< samp0 -.99))
	      (begin
	        ;; weird!  some of the clipped passages have "knees"
		;;   this looks nuts, but no time to scratch my head
		(set! clip-beg (1- clip-beg))
		(set! samp0 (vct-ref samps clip-beg))
		(if (or (> samp0 .99) (< samp0 -.99))
		    (begin
		      (set! clip-beg (1- clip-beg))
		      (set! samp0 (vct-ref samps clip-beg))))))
	  (if (or (> samp1 .99) (< samp1 -.99))
	      (begin
		(set! clip-end (1+ clip-end))
		(set! samp1 (vct-ref samps clip-end))
		(if (or (> samp1 .99) (< samp1 -.99))
		    (begin
		      (set! clip-end (1+ clip-end))
		      (set! samp1 (vct-ref samps clip-end))))))
          ;; now we have semi-plausible bounds
          ;; make sine dependent on rate of change of current 
	  (let* ((samp00 (vct-ref samps (1- clip-beg)))
		 (samp11 (vct-ref samps (1+ clip-end)))
		 (dist (- clip-end clip-beg))
		 (incr (/ pi dist))
		 (amp (* .125 (+ (abs (- samp0 samp00)) (abs (- samp1 samp11))) dist)))
	    (if (> samp0 0.0)
                ;; clipped at 1.0
		(do ((i (1+ clip-beg) (1+ i))
		     (angle incr (+ angle incr)))
		    ((= i clip-end))
		  (vct-set! samps i (+ 1.0 (* amp (sin angle)))))
                ;; clipped at -1.0
		(do ((i (1+ clip-beg) (1+ i))
		     (angle incr (+ angle incr)))
		    ((= i clip-end))
		  (vct-set! samps i (- -1.0 (* amp (sin angle))))))
	    (vct->channel samps (- clip-beg-1 4))))
	#t) ; return values so I can tell when I hit a 1-sample section during testing
      #f))

(define (fix-it n)
  ;; turn off graphics and fix all the clipped sections
  (set! (squelch-update) #t)
  (do ((i 0 (1+ i)))
      ((or (= i n) (c-g?))) 
      ;; "clips" here is a list form of the earlier vector of clip locations
    (fix-clip (list-ref clips (* i 2)) 
	      (list-ref clips (1+ (* i 2)))))
  (set! (squelch-update) #f))

(fix-it 669)

This produced 418 edits, with a maxamp of 2.26. So scale it back down: (scale-to .9). Next I ran some large ffts to see what sort of overall spectrum I had: (set! (transform-size) (expt 2 23)). This showed a massive DC component, and numerous harmonics of 60 Hz. I decided to get rid of the portions that were clearly noise. Since I was dealing with telephone recordings, I assumed anything under 40 Hz or above 4000 Hz was extraneous:

(define* (notch-out-rumble-and-hiss :optional snd chn)
  (let* ((cur-srate (exact->inexact (srate snd))))
    (filter-sound
     (list 0.0 0.0                    ; get rid of DC
	   (/ 80.0 cur-srate) 0.0     ; get rid of anything under 40 Hz (1.0=srate/2 here)
	   (/ 90.0 cur-srate) 1.0     ; now the passband
	   (/ 7000.0 cur-srate) 1.0 
	   (/ 8000.0 cur-srate) 0.0   ; end passband (40..4000)
	   1.0 0.0)                   ; get rid of some of the hiss
     ;; since I'm assuming the minimum band is 10 Hz here, 
     ;;   cur-srate/10 rounded up to next power of 2 seems a safe filter size
     ;;   filter-sound will actually use overlap-add convolution in this case
     (inexact->exact (expt 2 (ceiling (/ (log (/ cur-srate 10.0)) (log 2.0)))))
     snd chn)))

(notch-out-rumble-and-hiss)

By now it was obvious I needed a simple way to play portions of the sound before and after an edit, sometimes with a tracking cursor. So I bound a few keys:

(define (play-from-cursor current)
  (play (cursor) #f #f #f #f (if current #f (1- (edit-position)))))

(define (play-from-cursor-with-tracking current)
  ;; patterned after pfc in extsnd.html
  (let ((old-tracking (with-tracking-cursor)))
    (set! (with-tracking-cursor) #t)
    (add-hook! stop-playing-hook 
	       (lambda (snd)
		 (set! (with-tracking-cursor) old-tracking)))
    (play (cursor) #f #f #f #f (if current #f (1- (edit-position))))))

(bind-key #\p 0 (lambda () 
                  "play from cursor" 
	  	  (play-from-cursor #t) keyboard-no-action))
(bind-key #\P 0 (lambda () 
                  "play previous from cursor" 
		  (play-from-cursor #f) keyboard-no-action))
(bind-key #\p 4 (lambda () 
                  "play from cursor with tracking" 
		  (play-from-cursor-with-tracking #t) keyboard-no-action))

In words, if the mouse is in the channel graph, 'p' plays from the cursor, 'P' plays the previous version from the cursor, and 'C-p' plays from the cursor with a "tracking cursor". In several of the sections (the overall sound consisted of a couple dozen separate conversations), there was a loud mid-range tone. To figure out what its component frequencies were, I FFT'd a portion containing only that noise and got this spectrum (plus a zillion other peaks that didn't look interesting):

    ((425 .05) (450 .01) (546 .02) (667 .01) (789 .034) (910 .032) (470 .01))

To hear that, I passed this list to play-sines:

    (play-sines '((425 .05) (450 .01) (546 .02) (667 .01) (789 .034) (910 .032) (470 .01)))

And to my surprise, the result was close to the main portion of the hum. So now to notch out those frequencies, and see what is left: (notch-sound (list 425 450 470 546 667 789 910) #f 1 10). This erased most of the hum, but it also changed the timbre of the voices which wasn't acceptable. I goofed around with the notch-width and filter-size parameters, looking for something that that would still do the trick without removing the personal side of the voices, but in only a few cases was the result usable. What was being said was not very important, but the individual characteristics of each voice were.

The next step was to take out noisy sections between snippets, mostly using (env-selection '(0 1 1 0 10 0 11 1)) and equalizing each snippet, more or less, with scale-selection-by. There were a few "you-are-being-recorded" beeps which I deleted (via the Edit menu delete selection option). In some of the conversations, between sections of speech the background hum would gradually increase, then the voice would abruptly start with a large peak amplitude. These were fixed mostly with small-section scale-by's and envelopes. In the female voice sections, it seemed to help to: (filter-selection '(0 0 .01 0 .02 1 1 1) 1024) which got rid of some of the rumble without noticeably affecting the vocal timbre.


clicks: smooth-channel, remove-clicks, fft-smoother
rumble, hiss: notch-out-rumble-and-hiss, fft-squelch, fft-cancel
hum: notch-channel
via CLM ins: anoi




clm-ins

These are instruments from CLM translated for use in Snd. All expect to be called within with-sound or some equivalent environment. This set of instruments is a bit of a grab-bag; some are just examples of synthesis techniques; a few others are historical, rather than useful. If I were using, for example, the fm trumpet, I'd remove all the attack and decay parameters, moving that up a level to Common Music or whoever calls the trumpet, and combine several other parameters to reflect the desired output, rather than the details of the algorithm; 30 parameters could be reduced to less than 10, and the resulting instrument would be much easier to use. But, it is an historical artifact, so I'm reluctant to change it.

To try out any of these instruments, start Snd, set optimization to 6, load ws.scm and clm-ins.scm, then simply paste the with-sound call into the listener. It will automatically write the new sound file and open it in Snd.

anoi (file start dur :optional (fftsize 128) (amp-scaler 1.0) (r 6.28))
anoi is a stab at noise reduction based on Perry Cook's Scrubber.m. It tracks an on-going average spectrum, then tries to squelch that, obviously aimed at reducing background noise in an intermittent signal.
    (with-sound () (anoi "now.snd" 0 2))
attract (beg dur amp c)
attract is a translation to CLM of an instrument developed by James McCartney (CMJ vol 21 no 3 p 6), based on a "chaotic" equation. 'c' should be between 1 and 10 or thereabouts.
    (with-sound () (attract 0 1 .1 1) (attract 1 1 .1 5))
bes-fm (beg dur freq amp ratio index)
bes-fm is J1(J1): (bes-j1 (* index (bes-j1 phase))); it uses the Bessel functions where FM uses sinusoids. J0 is also good in this context, and the few other Jn options that I've tried were ok.
   Scheme:  (with-sound () (bes-fm 0 1 440 10.0 1.0 4.0))
   Ruby:    with_sound() do bes_fm(0, 0.5, 440, 5, 1, 8) end
So why does this work? My "back-of-the-envelope" guess is that the Bessel functions are basically a bump at the start followed by a decaying sinusoid, so the bump gives us a percussive attack, and the damped sinusoid gives us a dynamic spectrum, mimicking FM more or less. The Bessel functions I0, Jn, and Yn are built-in; Kn and In are implemented in Scheme in snd-test.scm. See bess and friends for many more examples.
canter (beg dur freq amp ...)
canter is half of a bagpipe instrument developed by Peter Commons (the other portion is drone below). The (required) trailing parameters are:
  deg dis pcrev ampfun ranfun skewfun skewpc ranpc ranfreq indexfun atdr dcdr
  ampfun1 indfun1 fmtfun1 ampfun2 indfun2 fmtfun2 ampfun3 indfun3 fmtfun3 ampfun4 indfun4 fmtfun4
Here is a portion of a bagpipe tune:
(let ((fmt1 '(0 1200 100 1000))
      (fmt2 '(0 2250 100 1800))
      (fmt3 '(0 4500 100 4500))
      (fmt4 '(0 6750 100 8100))
      (amp1 '(0 .67 100 .7))
      (amp2 '(0 .95 100 .95))
      (amp3 '(0 .28 100 .33))
      (amp4 '(0 .14 100 .15))
      (ind1 '(0 .75 100 .65))
      (ind2 '(0 .75 100 .75))
      (ind3 '(0 1 100 1))
      (ind4 '(0 1 100 1))
      (skwf '(0 0 100 0))
      (ampf '(0 0 25 1 75 1 100 0))
      (ranf '(0 .5 100 .5))
      (index '(0 1 100 1))
      (solid '(0 0 5 1 95 1 100 0))
      (bassdr2 '(.5 .06 1 .62 1.5 .07 2.0 .6 2.5 .08 3.0 .56 4.0 .24 5 .98 6 .53 7 
                 .16 8 .33 9 .62 10 .12 12 .14 14 .86 16 .12 23 .14 24 .17))
      (tenordr '(.3 .04 1 .81 2 .27 3 .2 4 .21 5 .18 6 .35 7 .03 8 .07 9 .02 10 .025 11 .035)))
  (with-sound (:reverb nrev)
    (drone .000 4.000 115.000 (* .25 .500) solid bassdr2 .100 .500 .030 45.000 1 .010 10)
    (drone .000 4.000 229.000 (* .25 .500) solid tenordr .100 .500 .030 45.000 1 .010 11)
    (drone .000 4.000 229.500 (* .25 .500) solid tenordr .100 .500 .030 45.000 1 .010 9)
    (canter .000 2.100 918 (* .25 .700) 45.000 1 .050 ampf ranf skwf 
             .050 .010 10 index .005 .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  2.100  .300 688.5  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  2.400  .040 826.2  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  2.440  .560 459  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.000  .040 408  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.040  .040 619.65  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.080  .040 408  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.120  .040 688.5  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.160  .290 459  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.450  .150 516.375  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.600  .040 826.2  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.640  .040 573.75  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.680  .040 619.65  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.720  .180 573.75  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.900  .040 688.5  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)
    (canter  3.940  .260 459  (* .25 .700)  45.000 1  .050 ampf ranf skwf
	     .050  .010 10 index  .005  .005 amp1 ind1 fmt1 amp2 ind2 fmt2 amp3 ind3 fmt3 amp4 ind4 fmt4)))
It is not easy to keep track of all these arguments in a long note-list; hence the development of programs such as Score (Leland Smith), Pla (yers truly), and Common Music (Rick Taube). The full note list is bag.clm in the CLM tarball.
cellon (beg dur freq amp ...)
cellon, developed by Stanislaw Krupowiecz, uses feedback FM as in some old synthesizers. There's a brief discussion of it in fm.html. The trailing parameters are:
    ampfun betafun beta0 beta1 betaat betadc ampat ampdc dis pcrev deg pitch1 glissfun glissat 
    glissdc pvibfreq pvibpc pvibfun pvibat pvibdc rvibfreq rvibpc rvibfun
and I actually don't know what they all do. I think they're dealing with attack and decay portions of envelopes; in the old days we felt we had to store one envelope, then kludge around with attack and decay timings to bash that envelope into the correct shape; this made instruments needlessly messy. Here's a call:
    (with-sound () 
      (cellon 0 2 220 .1 '(0 0 25 1 75 1 100 0) '(0 0 25 1 75 1 100 0) .75 1.0 0 0 0 0 1 0 0 220 
              '(0 0 25 1 75 1 100 0) 0 0 0 0 '(0 0 100 0) 0 0 0 0 '(0 0 100 0)))
The use of x axis values between 0 and 100, rather than 0.0 and 1.0 is a dead give-away that this is really ancient stuff.
clm-expsrc (beg dur input-file exp-ratio src-ratio amp :optional rev start-in-file)
clm-expsrc can stretch or compress a sound (using granular synthesis) while optionally changing its sampling rate. 'exp-ratio' sets the expansion amount (greater than 1.0 makes the sound longer), and 'src-ratio' sets the sampling rate change (greater than 1.0 makes it higher in pitch). So to make a sound twice as long, but keep the pitch the same:
    (with-sound () (clm-expsrc 0 4 "oboe.snd" 2.0 1.0 1.0))
'start-in-file' sets where we start reading the input file (in seconds); it defaults to 0.0.
drone (beg dur freq amp ampfun synth ampat ampdc amtrev deg dis rvibamt rvibfreq)
This is the other half of Peter Common's bagpipe — see canter above. 'synth' is a list of partials loaded into a table and read via table-lookup.
expandn time duration file amp ...

Here is the documentation from Rick Taube's granular synthesis page, edited slightly for the Scheme CLM.

The expandn instrument by Michael Klingbeil performs granular syntheisis by time-stretching (expanding/compressing) an input file. This effect is achieved by chopping up the input sound into very small segments (grains) that are then overlayed in the ouput stream. The larger the segments, the more the output sound is smeared, an effect approaching reverberation. The expandn instrument parameters are:

   time duration filename amplitude :key
	(expand 1.0)
	(matrix #f)
	(ramp 0.4)
	(seglen 0.15)
	(srate 1.0)
	(hop .05)
	(amp-env '(0 0 50 1 100 0))
	(input-start 0.0)
	(grain-amp 0.8)
	(reverb #f)

'time' is the start time of the sound in the output file. 'duration' is the duration of expanded sound. To expand an entire sound, set this to the expansion factor times the input sound's duration. 'filename' is the input file to expand. 'amplitude' is a scaler on the ampitude of the input file. Since the output is created by overlaying many copies of the intput this value is generally less than 1. 'hop' can be a number or an envelope. It is the average length in time between segments (grains) in the output. 'expand' can be a number or an envelope. It sets the amount of expansion to produce in the output file. 'seglen'can be a number or an envelope. It is the length in time of the sound segments (grains). 'srate' can be a number or an envelope. It sets the sampling rate change to apply to the output file. 'amp-env' is the amplitude envelope for the output sound. 'input-start' sets where to start reading in the input file. 'grain-amp' is a scaler on each grain's amplitude. 'matrix' is a list, a mixing matrix. 'reverb' is the reverb amount.

expfil (start duration hopsecs rampsecs steadysecs file1 file2)
expfile interleaves two granular synthesis processes (two readers pasting in tiny sections of their file, one after the other).
    (with-sound () 
      (expfil 0 2 .2 .01 .1 "oboe.snd" "fyow.snd")
      (expfil 2 2 .01 .01 .02 "oboe.snd" "fyow.snd"))
exp-snd (file beg dur amp :optional (exp-amt 1.0) (ramp .4) (seglen .15) (sr 1.0) (hop .05) ampenv)
exp-snd is a granular synthesis instrument with envelopes on the expansion amount ('exp-amt' as a list), segment ramp steepness ('ramp' as a list), segment length ('seglen' as a list), hop length ('hop' as a list), amplitude ('ampenv'), and resampling rate ('sr' as a list). In the next example, the expansion amount in both calls goes from 1 to 3 over the course of the note, the ramp time and segment lengths stay the same, the sampling rate changes from 2 to 0.5, and the hop stays the same (.05 in the first, and .2 in the second).
    (with-sound ()
      (exp-snd "fyow.snd" 0 3 1 '(0 1 1 3) 0.4 .15 '(0 2 1 .5) 0.05)
      (exp-snd "oboe.snd" 1 3 1 '(0 1 1 3) 0.4 .15 '(0 2 1 .5) 0.2))
fm-bell (beg dur frequency amplitude amp-env index-env index)
fm-bell is an FM instrument developed by Michael McNabb in Mus10 in the late '70s. It is intended for low bell sounds (say middle C or so). The lines
  (mod1 (make-oscil (* frequency 2)))
  (mod2 (make-oscil (* frequency 1.41)))
  (mod3 (make-oscil (* frequency 2.82)))
  (mod4 (make-oscil (* frequency 2.4)))
  (car1 (make-oscil frequency))
  (car2 (make-oscil frequency))
  (car3 (make-oscil (* frequency 2.4)))
set up three FM pairs, car1 and mod1 handling the basic harmonic spectra, car2 and mod2 creating inharmonic spectra (using the square root of 2 more or less at random), and car3 and mod3 putting a sort of formant at the minor third (2.4 = a ratio of 12/5 = octave+6/5 = minor tenth).
(with-sound ()
  (let ((fbell '(0 1 2 1.1000 25 .7500 75 .5000 100 .5000))
        (abell '(0 0 .1000 1 10 .6000 25 .3000 50 .1500 90 .1000 100 0)))
    (fm-bell 0.0 2.0 220.0 .5 abell fbell 0.5)))
fm-drum (beg dur freq amp ind :optional (high #f) (deg 0.0) (dist 1.0) (rev-amount 0.01))
The fm-drum uses "cascade FM" (see fm.html); it was developed by Jan Mattox.
    (with-sound () (fm-drum 0 1.5 55 .3 5 #f) (fm-drum 1.5 1.5 66 .3 4 #t))
fm-insect (beg dur freq amp ampenv modfreq modskew modenv index indexenv fmindex ratio deg dist rev)
The fm-insect started as an attempt to get cicada sounds from FM (for the 5th movement of "Colony"), but ended with:
(with-sound (:srate 22050) 
  (let ((locust '(0 0 40 1 95 1 100 .5))
	(bug_hi '(0 1 25 .7 75 .78 100 1))
	(amp    '(0 0 25 1 75 .7 100 0)))
    (fm-insect 0      1.699  4142.627  .015 amp 60 -16.707 locust 500.866 bug_hi  .346  .500)
    (fm-insect 0.195   .233  4126.284  .030 amp 60 -12.142 locust 649.490 bug_hi  .407  .500)
    (fm-insect 0.217  2.057  3930.258  .045 amp 60 -3.011  locust 562.087 bug_hi  .591  .500)
    (fm-insect 2.100  1.500   900.627  .06  amp 40 -16.707 locust 300.866 bug_hi  .346  .500)
    (fm-insect 3.000  1.500   900.627  .06  amp 40 -16.707 locust 300.866 bug_hi  .046  .500)
    (fm-insect 3.450  1.500   900.627  .09  amp 40 -16.707 locust 300.866 bug_hi  .006  .500)
    (fm-insect 3.950  1.500   900.627  .12  amp 40 -10.707 locust 300.866 bug_hi  .346  .500)
    (fm-insect 4.300  1.500   900.627  .09  amp 40 -20.707 locust 300.866 bug_hi  .246  .500)))
See animals.scm for much more convincing insect calls.
fm-trumpet (beg dur ...)
This is Dexter Morrill's FM-trumpet; see CMJ feb 77 p51.
    (with-sound () (fm-trumpet 0 .25))
As with many instruments from that era, it has a million parameters:
    beg dur :key (frq1 250.0) (frq2 1500.0) (amp1 0.5) (amp2 0.1)
    (ampatt1 0.03) (ampdec1 0.35) (ampatt2 0.03) (ampdec2 0.3)
    (modfrq1 250.0) (modind11 0.0) (modind12 2.66) 
    (modfrq2 250.0) (modind21 0.0) (modind22 1.8) 
    (rvibamp 0.007) (rvibfrq 125.0) (vibamp 0.007) (vibfrq 7.0) (vibatt 0.6) (vibdec 0.2)
    (frqskw 0.03) (frqatt 0.06) 
    (ampenv1 '(0 0  25 1  75 .9  100 0)) (ampenv2 '(0 0  25 1  75 .9  100 0)) 
    (indenv1 '(0 0  25 1  75 .9  100 0)) (indenv2 '(0 0  25 1  75 .9  100 0))
    (degree 0.0) (distance 1.0) (reverb-amount 0.005)
The pitch depends on the 'modfrq1' and 'modfrq2' parameters, as well as 'frq1' and 'frq2':
    (with-sound () (fm-trumpet 0 1 :frq1 400 :frq2 1600 :modfrq1 400 :modfrq2 400))
fofins (beg dur frq amp uvib f0 a0 f1 a1 f2 a2 :optional (amp-env '(0 0 1 1 2 1 3 0)))
fofins is an implementation of FOF synthesis, taken originally from fof.c of Perry Cook and the article "Synthesis of the Singing Voice" by Bennett and Rodet in "Current Directions in Computer Music Research" (MIT Press). FOF synthesis sets up a wave with the desired spectrum (to mimic vocal formats, for example), then calls wave-train to turn that into a tone. fofins just adds an amplitude envelope and vibrato. In the Scheme version, there is also an optional trailing vibrato envelope argument (this is slightly different from the CL version):
(with-sound ()  ; slowly ramp up the vibrato
  (fofins 0 4 270 .1 0.005 730 .6 1090 .3 2440 .1 
          '(0 0 .5 1 3 .5 10 .2 20 .1 50 .1 60 .2 85 1 100 0)
	  '(0 0 40 0 75 .2 100 1) )
  (fofins 0 4 (* 6/5 540) .1 0.005 730 .6 1090 .3 2440 .1 
          '(0 0 .5 .5 3 .25 6 .1 10 .1 50 .1 60 .2 85 1 100 0)
	  '(0 0 40 0 75 .2 100 1) )
  (fofins 0 4 135 .1 0.005 730 .6 1090 .3 2440 .1 
          '(0 0 1 3 3 1 6 .2 10 .1 50 .1 60 .2 85 1 100 0)
	  '(0 0 40 0 75 .2 100 1)))
fullmix (infile :optional beg outdur inbeg matrix srate reverb-amount)
fullmix is a complicated way to mix stuff. It's built into the CL version of CLM, so there was clamor for some sort of replacement in other versions of CLM. fullmix provides a mixer that can handle any number of channels of data in and out with scalers and envelopes on any path, sampling rate conversion, reverb — you name it! 'infile' is the file to be mixed:
    (with-sound () (fullmix "pistol.snd")) ; this places pistol.snd at time 0
'beg' is the start time of the mix in the output sound; 'outdur' is the duration of the mixed-in portion in the output; 'inbeg' is where to start the mix in the input file:
    (with-sound () (fullmix "pistol.snd" 1.0 2.0 0.25)) 
    ;; start at 0.25 in pistol.snd, include next 2 secs, put at time 1.0 in output
'srate' is the amount of sampling rate conversion to apply, and 'reverb' is the amount of the signal to send to the reverberator:
    (with-sound (:reverb nrev) (fullmix "pistol.snd" 1.0 2.0 0.25 #f 2.0 0.1)) ; up an octave, lots of reverb!
The 'matrix' parameter is much harder to describe. It is either a number or a list of lists. In the first case, that number is the amplitude scaler on the output:
    (with-sound (:reverb nrev) (fullmix "pistol.snd" 1.0 2.0 0.25 0.2 2.0 0.1)) ; same but much softer (0.2 amp)
If 'matrix' is a list of lists, each element of the inner lists can be either a number or list a breakpoints (an envelope). If a number, it is treated as an amplitude scaler for that input and output channel combination. Each inner list represents an input channel, so if we have a stereo input file going to a stereo output file and we want the channels to be mixed straight, but channel 0 at .5 amp and channel 1 at .75:
    (with-sound (:channels 2) (fullmix "2a.snd" #f #f #f '((0.5 0.0) (0.0 0.75))))
    ;;                                                       ^   ^     ^   ^
    ;;                                                       |   |     |   |
    ;;                                                    0->0   |  1->0   |
    ;;                                                        0->1      1->1
So, 2a.snd's first channel gets mixed into the output's first channel, scaled by 0.5, and its second channel goes to the output second channel scaled by 0.75. If we have four channels in and are writing a mono file, and want to mix in only the second channel of the input:
   (with-sound (:channels 1) (fullmix "4.aiff" #f #f #f '((0.0) (1.0) (0.0) (0.0))))
The next complication is that each entry in the inner lists can also be a list of envelope breakpoints. In that case, an envelope is applied to that portion of the mix, rather than just a scaler:
    (with-sound (:channels 2) (fullmix "oboe.snd" #f #f #f (list (list (list 0 0 1 1 2 0) 0.5))))
    ;; mono input so one list, envelope output chan 0, scale output chan 1 (two copies of input) 
And finally(!) each inner list element can also be a CLM env generator:
    (with-sound (:channels 2)
      (fullmix "oboe.snd" 1 2 0 (list (list .1 (make-env '(0 0 1 1) :duration 2 :scaler .5)))))
Here's a Ruby example:
    with_sound(:channels, 2, :statistics, true) do
      fullmix("pistol.snd")
      fullmix("oboe.snd", 1, 2, 0, [[0.1, make_env([0, 0, 1, 1], :duration, 2, :scaler, 0.5)]])
    end
Now we need filters!
gong (beg dur freq amp :key (degree 0.0) (distance 1.0) (reverb-amount 0.005))
gong is an FM instrument developed by Paul Weineke.
    Scheme:  (with-sound () (gong 0 3 261.61 .3))
    Ruby:    with_sound() do gong(0, 3, 261.61, 0.6) end
gran-synth (beg dur freq grain-dur grain-hop amp)
gran-synth sets up a wave-train playing an enveloped sinusoid (the "grain" in this case). 'grain-dur' sets the grain's length (in seconds), 'grain-hop' sets the frequency of the wave-train generator (how quickly the grain is repeated), and 'freq' sets the grain sinusoid's frequency.
    (with-sound () (gran-synth 0 1 300 .0189 .03 .4)) ; grain freq 300Hz, repetition rate 33Hz
graphEq (file :key beg dur or-beg amp (amp-env '(0 1.0 0.8 1.0 1.0 0.0)) (amp-base 1.0) ...)
graphEq is a sort of non-graphical graphical equalizer, developed by Marco Trevisani. It sets up a bank of formant generators with an optional envelope on each formant, then filters and envelopes the input file. Its trailing parameters are:
    (offset-gain 0)  
    (gain-freq-list '((0 1 1 0) 440 (0 0 1 1) 660))      
    (filt-gain-scale 1)                   
    (filt-gain-base 1)                    
    (a1 .99)
    (stats #t)
'a1' is the formant radius. 'gain-freq-list' is a list of gains and frequencies to filter The gains can be either numbers or envelopes (one or the other, not a mixture). 'offset-gain' is an offset (addition) to all the gains. 'filt-gain-scale' and 'filt-gain-base' are similar, but apply to the envelopes, if any. 'stats' prints encouraging numbers if #t.
    (with-sound () (graphEq "oboe.snd")) ; accept all the defaults (Scheme is case sensitive)
If we want just steady bands:
    (with-sound () (graphEq "oboe.snd" 0 0 0 1.0 '(0 1 1 0) 1.0 0 '(.1 440 .3 1500 .2 330)))
hammondoid (beg dur freq amp)
hammondoid is Perry Cook's additive-synthesis Hammond organ.
    (with-sound () (hammondoid 0 1 440 .1))
jl-reverb (:optional (decay 3.0))
jl-reverb is a cavernous version of John Chowning's ancient reverberator. You can never get enough reverb!
    (with-sound (:reverb jl-reverb) (fm-violin 0 .1 440 .1 :reverb-amount .1))
'decay' is the reverb decay time tacked onto the end of the output sound. To pass parameters to a reverberator, use the with-sound parameter :reverb-data. So, if we want 5 seconds of decay:
    (with-sound (:reverb jl-reverb :reverb-data '(5.0)) (fm-violin 0 .1 440 .1 :reverb-amount .1))
    ;;                                            ^ this is passed as (jl-reverb 5.0)
lbj-piano (beg dur freq amp :key (pfreq frequency) (degree 45) (reverb-amount 0) (distance 1))
lbj-piano, developed by Doug Fulton, uses James A Moorer's piano spectra and additive synthesis to mimic a piano.
    (with-sound () (lbj-piano 0 2 110.0 .2))
Doug says, "The high notes sound pretty rotten" and thinks perhaps one major problem is the lack of mechanical noise. 'pfreq' sets which spectrum to use; it defaults to whatever matches 'freq'.
    (with-sound () (lbj-piano 0 2 110.0 .2 :pfreq 550))
metal (beg dur freq amp)
metal is another Perry Cook creation (HeavyMtl); it's an FM instrument:
    (with-sound () (metal 0 1 440 .2))
nrev (:key (reverb-factor 1.09) (lp-coeff 0.7) (volume-1 1.0))
nrev, developed by Michael McNabb, is one of the more popular old-style reverbs. It is much cleaner than jc-reverb.
    (with-sound (:reverb nrev) (fm-violin 0 .1 440 .1 :reverb-amount .1))
'reverb-factor' controls the length of the decay — it should not exceed 1.21 or so. 'lp-coeff' controls the strength of the low pass filter inserted in the feedback loop. 'volume-1' can be used to boost the reverb output.
    (with-sound (:reverb nrev :reverb-data '(:lp-coeff 0.9 :volume-1 2.0)) 
      (fm-violin 0 .1 440 .1 :reverb-amount .1))
pins (beg dur file amp ...)
pins is a simple implementation of the spectral modeling synthesis of Xavier Serra and Julius Smith (sometimes known as "Sansy" or "SMS"). See Serra, X., J. O. Smith. 1990. "Spectral Modeling Synthesis:A Sound Analysis/Synthesis Based on a Deterministic plus Stochastic Decomposition". Computer Music Journal, vol. 14(4), 1990. The idea behind SMS is similar to the phase vocoder, but tracks spectral peaks so that its resynthesis options are much more sophisticated. The trailing parameters are:
  :key (transposition 1.0) (time-scaler 1.0) (fft-size 256) 
       (highest-bin 128) (max-peaks 16) printit attack
'transposition' can be used to transpose a sound; 'time-scaler' changes the sound's duration; 'fft-size' may need to be larger if your sampling rate is 44100, or the input sound's fundamental is below 300 Hz; 'highest-bin' sets how many fft bins we search for spectral peaks; 'max-peaks' sets how many peaks we track (at a maximum) through the sound; 'printit', if set to #t, causes the peak envelopes to be printied; 'attack' is an optional vct containing the attack portion of the new sound.
    Scheme:  (with-sound () (pins 0.0 1.0 "now.snd" 1.0 :time-scaler 2.0))
    Ruby:    with_sound() do pins(0, 1, "now.snd", 1, :time_scaler, 2) end
Xavier has a website devoted to this system, but it seems to move; search for CLAM or SMS.
pluck (beg dur freq amp :optional (weighting .5) (lossfact .9))
pluck is based on the Karplus-Strong algorithm as extended by David Jaffe and Julius Smith — see Jaffe and Smith, "Extensions of the Karplus-Strong Plucked-String Algorithm" CMJ vol 7 no 2 Summer 1983, reprinted in "The Music Machine". The basic idea is to fill an array with noise, then filter the array values as it is played repeatedly, giving a sharp attack and a ringing decay, much like plucking a guitar. The CMJ article gives many variations, changing pick position and so on. Jaffe's "Silicon Valley Breakdown" makes great use of this instrument. 'weighting' is the ratio of the once-delayed to the twice-delayed samples. It defaults to .5 which gives a short decay; anything other than .5 produces a longer decay. It should be between 0.0 and 1.0. 'lossfact' can be used to shorten decays. The most useful values are between .8 and 1.0.
(with-sound () 
  (pluck 0 1 330 .3 .95 .95) 
  (pluck .3 2 330 .3 .9 .9999) 
  (pluck .7 2 330 .3 .8 .99))
wavogram of pluck
In Ruby:
  with_sound() do pluck(0.05, 0.1, 330, 0.1, 0.95, 0.95) end
pqw-vox (beg dur freq spacing-freq amp ampfun freqfun freqscl phonemes formant-amps formant-shapes)
pqw-vox is an extension of Marc LeBrun's instrument vox (described below) to use phase-quadrature (single-sideband) waveshaping. It uses both Chebyshev polynomial kinds to set up spectra-producing pairs of waveshapers that will add in such a way as to cancel either the upper or lower set of sidebands. These are then ganged together as in the vox instrument to mimic moving formants.
    (with-sound () 
      (pqw-vox 0 1 300 300 .1 '(0 0 50 1 100 0) '(0 0 100 1) .3 '(0 L 100 L) '(.5 .25 .1) 
              '((1 1 2 .5) (1 .5 2 .5 3 1) (1 1 4 .5))))

    (with-sound ()
      (pqw-vox 0 2 200 200 .1 '(0 0 50 1 100 0) '(0 0 100 1) .1 '(0 UH 100 ER) '(.8 .15 .05) 
               '((1 1 2 .5) (1 1 2 .5 3 .2 4 .1) (1 1 3 .1 4 .5)))
      (pqw-vox 2 2 200 314 .1 '(0 0 50 1 100 0) '(0 0 100 1) .01 '(0 UH 100 ER) '(.8 .15 .05) 
               '((1 1 2 .5) (1 1 4 .1) (1 1 2 .1 4 .05)))
      (pqw-vox 4 2 100 414 .2 '(0 0 50 1 100 0) '(0 0 100 1) .01 '(0 OW 50 E 100 ER) '(.8 .15 .05) 
               '((1 1 2 .5 3 .1 4 .01) (1 1 4 .1) (1 1 2 .1 4 .05))))
pqw (beg dur freq spacing-freq carrier-freq amplitude ampfun indexfun partials ...)
pqw is a simple phase-quadrature waveshaping instrument which produces asymmetric spectra. The trailing parameters just set the usual degree, distance, and reverb values.
    (with-sound () (pqw 0 .5 200 1000 .2 '(0 0 25 1 100 0) '(0 1 100 0) '(2 .1 3 .3 6 .5)))
To see the asymmetric spectrum most clearly, set the index function above to '(0 1 100 1).
resflt (beg dur driver ...)
resflt, developed by Richard Karpen and Xavier Serra, sets up three resonators (two-pole filters), then drives them with either white noise or a sum-of-cosines pulse train. Both can be used for vocal effects:
    (with-sound () 
      (resflt 0 1.0 0 0 0 #f .1 200 230 10 '(0 0 50 1 100 0) '(0 0 100 1) 
              500 .995 .1 1000 .995 .1 2000 .995 .1)
      (resflt 1 1.0 1 10000 .01 '(0 0 50 1 100 0) 0 0 0 0 #f #f 
              500 .995 .1 1000 .995 .1 2000 .995 .1))
The trailing parameters are:
     ranfreq noiamp noifun cosamp cosfreq1 cosfreq0 cosnum ampcosfun freqcosfun 
     frq1 r1 g1 frq2 r2 g2 frq3 r3 g3
     :key (degree 0.0) (distance 1.0)(reverb-amount 0.005)
Set 'driver' to 0 to get the pulse train, or to 1 to get white noise. In the latter case, 'ranfreq' is the random number generator frequency, 'noiamp' is its amplitude, and 'noifun' is an amplitude envelope on its output (filter input) In the pulse case, 'cosamp' is the pulse train amplitude, 'ampcosfun' the amplitude envelope, 'cosfreq0' and 'cosfreq1' set the frequency limits of 'freqcosfun', and 'cosnum' sets the number of cosines in the pulse. The three resonators are centered at 'frq1', 'frq2', 'frq3', with pole-radius 'r1', 'r2', and 'r3' respectively, and with gains of 'g1', 'g2', and 'g3'.
reson (beg dur freq amp ...)
reson is a vocal simulator developed by John Chowning. Its trailing parameters are:
    numformants indxfun skewfun pcskew skewat skewdc vibfreq vibpc ranvibfreq ranvibpc 
    degree distance reverb-amount data

    'data' is a list of lists of form 
      '(ampf resonfrq resonamp ampat ampdc dev0 dev1 indxat indxdc)
Needless to say, no one has ever written out these parameters by hand, so here's an all-time first:
    (with-sound ()
      (reson 0.0 1.0 440 .1 2 '(0 0 100 1) '(0 0 100 1) .1 .1 .1 5 .01 5 .01 0 1.0 0.01
   	     '(((0 0 100 1) 1200 .5 .1 .1 0 1.0 .1 .1) ((0 1 100 0) 2400 .5 .1 .1 0 1.0 .1 .1))))
But JC got very nice vocal sounds from this — I must have mistyped somewhere... Here's another stab at it:
(with-sound ()
      (reson 0.0 1.0 440 .1 2 '(0 1 100 0) '(0 0 100 1) .01 .1 .1 5 .01 5 .01 0 1.0 0.01
   	     '(((0 1 100 1) 1000 .65 .1 .1 0 1.0 .1 .1) ((0 0 100 1) 2400 .15 .1 .1 0 1.0 .1 .1))))
If you find a good example, please send me it!
rhodey (beg dur freq amp :optional (base .5))
rhodey is another of Perry Cook's instruments (an electric piano), based on a pair of FM generators.
    (with-sound () (rhodey 0 1 440 .2))
One of the oscillators is set to a frequency 15 times the requested 'freq', so for higher notes, you'll need to set the srate higher:
    (with-sound (:srate 44100) (rhodey 0 1 880 .2))
rms gen sig
balance gen sig comparison
gain gen sig rsmval
make-rmsgain :optional (hp 10.0)
rms, balance, and gain are an implementation of the balance generators of CLM (based on CSound originals, Scheme versions originally provided by Fabio Furlanete). This section is a paraphrase of balance.html in the CLM tarball which was written by Sam Hiesz. balance, rms, and gain are used to track the RMS value of a signal and use that information to scale some other signal. rms returns the RMS value; gain takes a signal and an RMS value and modifies the signal to track the RMS value; balance packages gain and rms into one function call. make-rmsgain returns the generator used by rms, gain, and balance. The 'hp' parameter sets the speed with which the balance process tracks the RMS signal. An example is worth a zillion words:
      (with-sound (:channels 3)
        (let ((rg (make-rmsgain))
	      (rg1 (make-rmsgain 40))
	      (rg2 (make-rmsgain 2))
	      (e (make-env '(0 0 1 1 2 0) :length 10000))
	      (e1 (make-env '(0 0 1 1) :length 10000))
	      (e2 (make-env '(0 0 1 1 2 0 10 0) :length 10000))
	      (o (make-oscil 440.0)))
	  (do ((i 0 (1+ i)))
	      ((= i 10000))
	    (let ((sig (env e)))
	      (outa i (balance rg sig (env e2)))
	      (outb i (balance rg1 sig (env e1)))
	      (outc i (balance rg2 (* .1 (oscil o)) (env e2)))))))
scratch (beg file src-ratio turnlist)
scratch moves back and forth in a sound file according to a list of turn times much like env-sound-interp. With voice input, we can create a "Remembrance of Bugs Bunny":
    Scheme: (with-sound () (scratch 0.0 "now.snd" 1.5 '(0.0 .5 .25 1.0)))
    Ruby:   with_sound() do scratch(0, "now.snd", 1.5, [0.0, 0.5, 0.25, 1.0]) end
I translate this as: "go forward from 0.0 to 0.5 secs, backwards to 0.25 secs, then forward to 1.0 secs".
spectra (beg dur freq amp ...)
spectra is an additive-synthesis instrument with vibrato and an amplitude envelope. It was intended originally to be used with the spectra in spectra.scm (information laboriously gathered at the dawn of the computer era by James A Moorer). One such spectrum is labelled "p-a4", so we can hear it via:
    (load "spectr.scm")
    (with-sound () 
      (spectra 0 1 440.0 .1 p-a4 '(0.0 0.0 1.0 1.0 5.0 0.9 12.0 0.5 25.0 0.25 100.0 0.0)))
The trailing parameters are:
 :optional (partials '(1 1 2 0.5))
           (amp-envelope '(0 0 50 1 100 0))
           (vibrato-amplitude 0.005)
           (vibrato-speed 5.0)
           (degree 0.0)
           (distance 1.0)
           (reverb-amount 0.005)
We can pass our own partials:
    (with-sound ()
      (spectra 0 1 440.0 .1 '(1.0 .4 2.0 .2 3.0 .2 4.0 .1 6.0 .1) 
               '(0.0 0.0 1.0 1.0 5.0 0.9 12.0 0.5 25.0 0.25 100.0 0.0)))
ssb-fm gen modsig
make-ssb-fm freq
These two functions implement a sort of asymmetric FM using ideas similar to those used in ssb-am.
stereo-flute (beg dur freq flow ...)
This is a physical model of a flute developed by Nicky Hind.
    Scheme:
    (with-sound (:channels 2) 
       (stereo-flute 0 1 440 0.55 :flow-envelope '(0 0 1 1 2 1 3 0))
       (stereo-flute 1 3 220 0.55 :flow-envelope '(0 0 1 1 2 1 3 0)))

    Ruby:
    with_sound() do stereo_flute(0, 2, 440, 0.55, :flow_envelope, [0, 0, 1, 1, 2, 1, 3, 0]) end
The trailing parameters are:
:key (flow-envelope '(0 1 100 1))
     (decay 0.01) 		; additional time for instrument to decay
     (noise 0.0356) 
     (embouchure-size 0.5)
     (fbk-scl1 0.5)		; these two are crucial for good results
     (fbk-scl2 0.55)
     (offset-pos 0.764264)      ; from 0.0 to 1.0 along the bore
     (out-scl 1.0)
     (a0 0.7) (b1 -0.3)	        ; filter coefficients
     (vib-rate 5) 
     (vib-amount 0.03)
     (ran-rate 5) 
     (ran-amount 0.03)
As with physical models in general, you may need to experiment a bit to find parameters that work.
touch-tone (beg number)
This instrument produces telephone tones:
    Scheme:  (with-sound () (touch-tone 0.0 '(7 2 3 4 9 7 1)))
    Ruby:    with_sound() do touch_tone(0, [7, 2, 3, 4, 9, 7, 1]) end
It is just two sine waves whose frequencies are chosen based on the number pressed.
  1     2     3   697 Hz
  4     5     6   770 Hz
  7     8     9   852 Hz
        0         941 Hz
 1209  1336  1477 Hz
For more than you really want to know about other such sounds, see Telephone Tone Frequencies.
tubebell beg dur freq amp :optional (base 32.0)
Perry Cook's tubular bell:
    (with-sound () 
      (tubebell 0 2 440 .1 32.0) 
      (tubebell 2 2 220 .1 64.0) 
      (tubebell 4 2 660 .1 .032))
'base' is the envelope base:
    (with-sound () 
      (tubebell 0 2 440 .1 32.0) 
      (tubebell 2 2 220 .1 2048.0) 
      (tubebell 4 3 660 .1 .032))
two-tab beg dur freq amp ...
two-tab interpolates between two spectra.
    (with-sound () (two-tab 0 2 440 .1 '(1.0 1.0) '(3.0 1.0)))
    ;; go from harmonic 1 to harmonic 3
The trailing parameters are:
:optional (partial-1 '(1.0 1.0 2.0 0.5))
          (partial-2 '(1.0 0.0 3.0 1.0))
          (amp-envelope '(0 0 50 1 100 0))
          (interp-func '(0 1 100 0))
          (vibrato-amplitude 0.005)
          (vibrato-speed 5.0)
          (degree 0.0)
          (distance 1.0)
          (reverb-amount 0.005)
'interp-func' determines how we interpolate between the two spectra. When it is at 1.0, we get only the first, at 0.0 only the second.
    (with-sound () (two-tab 0 2 440 .1 '(1.0 1.0) '(3.0 1.0) '(0 0 1 1 2 0) '(0 0 1 1)))
is the reverse of the earlier sound. To go out and back:
    (with-sound () (two-tab 0 2 440 .1 '(1.0 1.0) '(3.0 1.0) '(0 0 1 1 2 0) '(0 0 1 1 2 0)))
vox (beg dur freq amp ampfun freqfun freqscl voxfun index vibscl)
vox is a translation of Marc LeBrun's MUS10 waveshaping voice instrument using FM in this case. The basic idea is that each of the three vocal formants is created by two sets of waveshapers (or oscils producing FM), one centered on the even multiple of the base frequency closest to the desired formant frequency, and the other on the nearest odd multiple. As the base frequency moves (due to vibrato or glissando), these center frequencies are recalculated on each sample, and the respective amplitudes set to reflect the distance of the current center frequency from the desired formant frequency. If a center frequency moves enough that the previous upper member of the pair has to become the lower member, the upper waveshaper (which has meanwhile ramped to zero amplitude), jumps down to its new center. The male-speaker formant table was provided by Robert Poor (see the code for the complete table of formants). For details on waveshaping, see Le Brun, "Digital Waveshaping Synthesis", JAES 1979 April, vol 27, no 4, p250. I used vox in the 5th movement of "Colony" and in "The New Music Liberation Army".
(with-sound ()
  (let ((amp-env '(0 0 25 1 75 1 100 0))
        (frq-env '(0 0 5 .5 10 0 100 1)))
  (vox 0 2 170 .4 amp-env frq-env .1 
    '(0 E 25 AE 35 ER 65 ER 75 I 100 UH) '(.8 .15 .05) '(.005 .0125 .025) .05 .1)
  (vox 2 2 110 .4 amp-env frq-env .5 
    '(0 UH 25 UH 35 ER 65 ER 75 UH 100 UH) '(.8 .15 .05) '(.005 .0125 .025))
  (vox 4 2 300 .4 amp-env frq-env .1 
    '(0 I 5 OW 10 I 50 AE 100 OO) '(.8 .15 .05) '(.05 .0125 .025) .02 .1)))
Or in Ruby:
with_sound() do
  amp_env = [0, 0, 25, 1, 75, 1, 100, 0]
  frq_env = [0, 0, 5, 0.5, 10, 0, 100, 1]
  vox(0, 2, 170, 0.4, amp_env, frq_env, 0.1, 
       [0, :E, 25, :AE, 35, :ER, 65, :ER, 75, :I, 100, :UH], 0.05, 0.1)
  vox(2, 2, 300, 0.4, amp_env, frq_env, 0.1, 
       [0, :I, 5, :OW, 10, :I, 50, :AE, 100, :OO], 0.02, 0.1)
  vox(4, 5, 600, 0.4, amp_env, frq_env, 0.1, 
       [0, :I, 5, :OW, 10, :I, 50, :AE, 100, :OO], 0.01, 0.1)
end

vox can also be use for less vocal effects:

(with-sound (:play #t :scaled-to .5)
  (vox 0 .25 500 .4 '(0 0 .1 1 1 1 2 .5 3 .25 10 0) '(0 0 5 .5 10 0 100 1) .1 
       '(0 E 25 OW 35 ER 105 ER) '(.13 .15 .15) '(.005 .005 .015) .05 .1))
wurley beg dur freq amp
Perry Cook's Wurlitzer (I assume).
    (with-sound () (wurley 0 1 440 .1))
za time dur freq amp length1 length2 feedback feedforward
zc time dur freq amp length1 length2 feedback
zn time dur freq amp length1 length2 feedforward
The "z" instruments demonstrate "zdelay" effects — interpolating comb, notch, and all-pass filters.
    (with-sound () (zn 0 1   100 .1 20 100 .995) 
		   (zn 1.5 1 100 .1 100 20 .995)
		   (zc 3 1   100 .1 20 100 .95) 
		   (zc 4.5 1 100 .1 100 20 .95)
		   (za 6 1   100 .1 20 100 .95 .95) 
		   (za 7.5 1 100 .1 100 20 .95 .95))

snd-test.scm has examples of calling all these instruments. For more examples of instruments, there are a variety of separate files such as v.scm, and clm23.scm has a translation of the CLM test instruments. It also has some comments about the differences between the CL and Scheme instruments.

see also: bird clm dlocsig examp fade fm fmv freeverb graphEq grani jcrev maraca 
          maxf noise piano prc95 pvoc rt-examples singer sndwarp stochastic strad ws


debug

  snd-debug               ; enable some command shortcuts
  snd-trace               ; enable trace output
  snd-break (message #f)  ; set a breakpoint

debug.scm is a package of Guile-specific debugging aids. snd-break sets a breakpoint; if it is called, you drop into the Snd debugger. You can continue from the breakpoint, optionally returning any value you like. While in the break context (while the listener prompt says "break"), these functions are available:

  break-go (returned-value #f)    ; continue execution from breakpoint, return 'returned-value'
  break-locals (stack-location 0) ; display local variables 
  break-local local-var (stack-location 0) ; print a local's value ('local-var' is a symbol or string)
  break-backtrace (all #f)        ; display stack at breakpoint (5 levels by default, 'all' = #t: all levels)
  break-help                      ; display some help
  break-quit                      ; exit current break level
  break-quit!                     ; exit all break levels, return to top level

Here is a brief session in Snd's listener:

    :(define (test-break a) (let ((b (+ a (snd-break "hiho")))) b))
    #<unspecified>
    :(define hi 123)
    #<unspecified>
    :(set! hi (test-break 1))
    break:("hiho")
    break:hi
    123
    break:(break-go 32)
    :#<unspecified>
    :hi
    33

We put a breakpoint in the midst of an expression in the 'test-break' function, asking it to type "hiho" and drop into the debugger if it is executed. Then we call 'test-break' in an expression that sets the variable 'hi'. The breakpoint is hit, "hiho" gets reported, and we're placed in the debugger. The debugger is just the Snd listener, but with some extra context to implement the break support. After poking around, we call break-go with an argument of 32. This causes the original set! to continue with 32 plugged in where the snd-break call was, setting hi to 33.

snd-debug sets up some information needed by the 'bt' and 'lv' commands:

  bt                 ; show backtrace
  lv (:optional obj) ; show local var(s)

These are active outside a breakpoint, so once set up, you can use them whenever an error occurs. snd-trace activates any tracing that you may have requested and redirects its output to the Snd listener. Here's how to trace fm-violin calls in a notelist:

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

To turn off the trace

    (untrace fm-violin)

dlocsig

dlocsig is a CLM generator developed by Fernando Lopez-Lezcano that can move sounds in two or three dimensions. Fernando's CLM/lisp-oriented documentation can be found in dlocsig.html. dlocsig.rb is Michael Scholz's translation of dlocsig to Ruby. It has lots of documentation and examples. If you load dlocsig.rb, a new menu is added named "Dlocsig". If you choose a path from this menu, you get a graphical user-interface to play with the various envelopes that drive dlocsig. Click the "With_Snd" button to apply the current path choices to the currently selected sound. Click "Gnuplot" to get a pretty picture of the path (in 3D!). A simple instrument that uses dlocsig is:

(define* (sinewave start-time duration freq amp :key (amp-env '(0 1 1 1))
		   (path (make-path :path '(-10 10 0 5 10 10))))
  (let* ((vals (make-dlocsig :start-time start-time :duration duration :path path))
	 (dloc (car vals))
	 (beg (cadr vals))
	 (end (caddr vals)))
    (let* ((osc (make-oscil freq))
	   (aenv (make-env amp-env :scaler amp :duration duration)))
      (run
       (lambda ()
	 (do ((i beg (1+ i)))
	     ((= i end))
	   (dlocsig dloc i (* (env aenv) (oscil osc)))))))))

(with-sound (:channels 2) (sinewave 0 1.0 440 .5 :path (make-path '((-10 10) (0.5 0.5) (10 10)) :3d #f)))


draw

draw.scm has examples of graphics-oriented extensions. The most useful one is make-current-window-display.

click-for-listener-help (pos)
click-for-listener-help is intended as a listener-click-hook function. It posts help about the closest entity it can find whenever you double click in the listener. Unfortunately, the help dialog is a bit clunky for a use like this, but the minibuffer has only one line, and tooltips are irritating in their own way; perhaps it should post the help at the bottom of the listener?
color-samples (color :optional beg dur snd chn)
uncolor-samples (:optional snd chn)
color-samples displays the samples from sample 'beg' for 'dur' samples in 'color' whenever they're in the current time domain view. uncolor-samples cancels this action. Here is a representative picture. To activate this, add it to after-graph-hook.
display-previous-edits (snd chn)
display-previous-edits displays all the edits of the current sound, with older edits gradually fading away. To activate this, add it to after-graph-hook:
    (add-hook! after-graph-hook display-previous-edits)
make-current-window-display ()
make-current-window-display displays in the upper right corner the overall current sound and where the current window fits in it. This information is implicit in the x axis zoom and position sliders, but a redundant graph doesn't hurt. If you click in that graph, the cursor is moved to the clicked point. A menu item named "Toggle inset" is added to the View menu to turn this little graph off or on. make-current-window-display
If you're using a line cursor (if cursor-style is cursor-line), it may collide at times with the little graph; to get a more polite cursor, use smart-line-cursor.
overlay-rms-env (snd chn)
overlay-rms-env displays the running rms value of the currently displayed data in red, overlayed upon the normal graph. To activate it, add it to the after-graph-hook:
    (add-hook! after-graph-hook overlay-rms-env)
overlay-sounds (:rest sounds)
overlay-sounds overlays onto its first argument (a sound index) all subsequent arguments: (overlay-sounds 1 0 3).
samples-via-colormap (snd chn)
samples-via-colormap displays the time domain graph using the current colormap (it is really just an example of colormap-ref). To activate this, add it to after-graph-hook:
    (add-hook! after-graph-hook samples-via-colormap)
samples-via-colormap
smart-line-cursor (snd chn tracking)
smart-line-cursor is a cursor-style function that tries not to overwrite the thumbnail graph drawn by make-current-window-display.