Snd Customization and Extension


related documentation: snd.html grfsnd.html sndscm.html sndclm.html fm.html sndlib.html libxm.html index.html
this file:grfsnd.html:


Introduction

Snd is a highly customizable, extensible program. I've tried to bring out to the extension language nearly every portion of Snd, both the signal-processing functions and much of the user interface. You can, for example, add your own menu choices, editing operations, or graphing alternatives. Nearly everything in Snd can be set in an initialization file, loaded at any time from a text file of program code, or specified in a saved state file. It can also be set via inter-process communication or from stdin from any other program (CLM and Emacs in particular), embedded in a keyboard macro, or typed in the listener.

The syntax used throughout this documentation is Scheme (a form of lisp) as implemented by S7, or Guile. You can also use Ruby or Forth, but need to make various minor changes. I'm slowly adding parallel Forth and Ruby examples.

The easiest way to get acquainted with this aspect of Snd is to open the listener (via the View:Open listener menu option), and type experiments in its window. Its prompt is ">". So, say we've opened the listener (my typing is in this color and Snd's responses are in this color):

SchemeRubyForth
>(+ 1 2)
3
>1+2
3
>1 2 +
3
>(open-sound "oboe.snd")
0
>open_sound("oboe.snd")
0
>"oboe.snd" open-sound
0
>(auto-resize)
#t
>auto_resize
true
>auto-resize
#t
>(set! (auto-resize) #f)
#f
>set_auto_resize false
false
>#f set-auto-resize
#f
>(set! (x-bounds) '(0.0 1.0))
(0.0 1.0)
>set_x_bounds([0.0, 1.0])
0.01.0
>'( 0.0 1.0 ) set-x-bounds
'( 0.0 1.0 )
>(load "bird.scm")
#<unspecified>
>load "bird.rb"
true
>"bird.fs" file-eval
0
>(map-channel (lambda (y) (* y 2)))
0
>map_channel(lambda do |y| y * 2 end)
-0.0015869140625
>lambda: <{ y }> y 2.0 f* ; map-channel
-0.00158691
>(define (plus a b) (+ a b))
#<unspecified>
>def plus(a, b) a+b end

>: plus ( a b -- sum ) { a b } a b + ;
nil
>(set! (basic-color) (make-color 1 0 0))
(Pixel 16711680)
>set_basic_color make_color(1, 0, 0)
[:Pixel, 16711680]
>1 0 0 make-color set-basic-color
#<XmRaw: Pixel 0x9d3b430>

Another quick way to check out the extension language is to go to the Preferences dialog (in the Options menu), choose some items, then save them. The saved file (~/.snd_prefs_guile for example) is a text file, a program in the current extension language, that initializes Snd to use whatever items you chose.

If the listener is active, and some sound is selected, any characters typed while in the sound graph which it can't handle are passed to the listener; to exit the listener without using the mouse, type C-g. This is also the way to get back to the listener prompt if it appears to be hung; normally in this situation, it's actually waiting for a close paren; if you put the cursor just past the close paren you're interested in and type return, Snd will flash the unbalanced open paren (if any) briefly. In any case, whenever the cursor is just past a close paren, the matching open paren is underlined.


Snd Programming

Snd is organized as a list of sounds, each with a list of channels, each channel containing lists of edits, marks, mixes, etc. There are other objects such as colors, vcts (an optimization of vectors), and regions; the currently active region is called the selection. I originally presented all the functions and variables in an enormous alphabetical list, but that finally became unmanageable. In the following sections, each of the basic entities is treated in a separate section with cross-references where needed. The index provides alphabetical entry.

There are many examples in examp.scm, examp.rb, examp.fs, and snd-test.scm. Extensions to Snd can be found in:

Contents
analog-filter standard IIR filters
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
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 a bunch of generators
grani CLM's grani (Fernando Lopez-Lezcano and 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|10.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)


Customizing Snd's behavior

Most of Snd's behavior can be customized. For example, when a sound is saved, some people want to be warned if a pre-existing sound is about to be destroyed; others (Snd's author included) grumble "just do it". There are two ways this kind of situation is handled in Snd; through global variables and hooks. A hook is a list of callbacks invoked whenever its associated event happens. When Snd exits, for example, any functions found on the before-exit-hook list are evaluated; if any of them returns #t, Snd does not exit.

(define (unsaved-edits? lst)
  (if (not (null? lst))
      (if (> (car (edits (car lst))) 0)
	  (begin
	    (report-in-minibuffer "there are unsaved edits")
	    #t)
	  (unsaved-edits? (cdr lst)))
      #f))

(add-hook! before-exit-hook (lambda () (unsaved-edits? (sounds))))

Now when Snd is told to exit, it checks before-exit-hook, runs unsaved-edits?, and if the latter returns #t, if prints a worried message in the minibuffer, and refuses to exit. Similar hooks customize actions such as closing a sound (close-hook), clicking a mark (mark-click-hook), pressing a key (key-press-hook), and so on.


Global variables

The global variables handle various customizations that aren't callback-oriented. For example, as panes (sounds) come and go, Snd's overall size may change (this is partly determined by the window manager, but is also up to Snd); many people find this distracting — they would rather that the overall window stick to one size. The Snd variable associated with this is "auto-resize"; it can be accessed as:

  Scheme: (auto-resize)
  Ruby:   auto_resize()
  Forth:  auto-resize

and set via:

  Scheme: (set! (auto-resize) #f)
  Ruby:   set_auto_resize(false)
  Forth:  #f set-auto-resize

The variables are presented as a special kind of function, rather than as bare variables, mainly to ensure that Snd's response to the assignment is immediate. The statement (set! (auto-resize) #f) can be placed in your ~/.snd initialization file to make it the default setting for your version of Snd, or placed in a separate file of Scheme code and loaded at any time via the load function.

The variables affecting Snd's overall behavior are:

ask-before-overwrite #f
ask-before-overwrite determines whether Snd asks before overwriting an existing file: (set! (ask-before-overwrite) #t)
audio-input-device mus-audio-default
This is the recorder's input device: (set! (audio-input-device) mus-audio-microphone).
audio-output-device mus-audio-default
This is the audio output device for the play button.
auto-resize #t
auto-resize determines whether the Snd window should be resized when a sound is opened or closed.
auto-update #f
auto-update determines whether Snd should update a file automatically if it (the file) changes on disk due to some other process. If Snd's view of a sound doesn't match the on-disk version of the sound, a bomb icon warns you that there are two conflicting versions of the sound.
auto-update-interval 60
This is the time (in seconds) between background checks for a changed file on disk (see auto-update). If auto-update-interval is 0.0, the auto-update background process is turned off. If the file alternation monitor is running (the default if you have libfam or libgamin), auto-update-interval is ignored since in that case the check happens instantly.
clipping #f
If clipping is #t, output values are clipped to fit the current sndlib sample representation's notion of -1.0 to just less than 1.0. The default (#f) can cause wrap-around (if writing integer sound data) which makes the out-of-range values very obvious. To control this action more closely, use clip-hook. To get completely confused, see mus-clipping and mus-file-clipping: this has become as messed up as the sampling rate settings!
cursor-location-offset 0.05
cursor-location-offset is the offset in samples between Snd's notion of the location of the tracking cursor (with-tracking-cursor in Snd jargon) and the actual (DAC-relative) location. Since, in general, Snd can't tell how many samples of buffering there are between itself and the speakers (audio cards have varying amounts), its notion of where to place the tracking cursor can be wrong by an almost arbitrary amount. If you have some idea of the buffering amount, you can correct this error via cursor-location-offset.
cursor-update-interval 0.05
This is the time in seconds between cursor redisplays if playing a sound with with-tracking-cursor #t. If this number is too small, you may clicks during playback.
dac-combines-channels #t
If dac-combines-channels is #t, and the current sound has more channels than are supported by the available audio hardware, Snd mixes the extra channels into the available channels during audio output. This provides a way to hear 4-channel sounds when you only have a stereo audio card. If dac-combines-channels is #f, extra channels are not played.
dac-size 256
dac-size is the audio output buffer size; it is not always meaningful. See play-with-envs in enved.scm or play-sound in play.scm. When you change the control panel settings during playback, the snappiness of the response is set, to some extent, by the dac-size. The default of 256 gives a stair-case effect in many cases, whereas 2048 is smoother. This also affects the resampling smoothness of playback while dragging the mark play triangle. Some audio choices, ALSA in particular, may ignore dac-size.
default-output-chans 1
default-output-chans is the default number of channels when a new or temporary file is created, or a save dialog is opened.
default-output-data-format mus-bfloat
default-output-data-format is the default data format when a new or temporary file is created, or a save dialog is opened. (The default, mus-bfloat, is from sndlib, standing for 32-bit big-endian floating point data). Use mus-out-format for fastest IO. The available output data formats are (b=big-endian, l=little, u=unsigned, short=16 bits, byte=8 bits, int = 32 bits):
    mus-bshort  mus-lshort mus-mulaw  mus-alaw   mus-byte   mus-ubyte   mus-bfloat
    mus-lfloat  mus-bint   mus-lint   mus-b24int mus-l24int mus-bdouble mus-ldouble
    mus-ubshort mus-ulshort
There are also "unscaled" versions of the floating point types, and "normalized" versions of the integers.
default-output-header-type mus-next
This is the default header type when a new file is created, or a save dialog is opened. (The default, mus-next, stands for the NeXT/Sun sound file header). The available output header-types are:
    mus-next mus-aifc mus-riff mus-rf64 mus-nist mus-raw mus-ircam mus-aiff 
    mus-soundfont mus-bicsf mus-voc mus-svx mus-caff
default-output-srate 44100
This is the default sampling rate when a new or temporary file is created, or a save dialog is opened.
eps-bottom-margin 0.0
eps-bottom-margin is the bottom margin used in snd.eps, created by the File:Print dialog, or the graph->ps function. PostScript units are 1/72 of an inch (a "point" in printer jargon); an inch is 2.54 cm:
Scheme:

(define (inches-to-ps inches) 
  (* inches 72))

(define (cm-to-ps cm) 
  (* cm (/ 72.0 2.54)))
Ruby:
def inches_to_ps(inches) 
  inches * 72 
end
def cm_to_ps(cm) 
  cm * 72.0 / 2.54 
end
Forth:
: inches-to-ps { inches } 
  inches 72 f* 
;
: cm-to-ps { cm } 
  cm 2.54 f/ 72 f* 
;
In the resulting .eps file, you'll find a concat statement near the top of the file; the first and fourth numbers are scale factors on the entire graph, the fifth is the left margin, and the sixth is the bottom margin.
eps-file "snd.eps"
This is the default name of the Postscript file produced by the File:Print dialog, or the graph->ps function.
eps-left-margin 0.0
eps-left-margin is the left margin used in snd.eps, created by the File:Print dialog, or the graph->ps function.
eps-size 1.0
eps-size is the scaler used to set the overall picture size in snd.eps, created by the File:Print dialog, or the graph->ps function,
graph-cursor XC_crosshair (34)
graph-cursor is the kind of cursor displayed following the mouse in the data graph. It can be any of the cursors provided by X or Gtk+: (set! (graph-cursor) 22). The X/Motif cursors are declared in /usr/include/X11/cursorfont.h or some such file; gtk versions are in gdkcursor.h. Some useful choices are:
MotifGtk+value
XC_arrowGDK_ARROW2
XC_center_ptrGDK_CENTER_PTR22
XC_crossGDK_CROSS30
XC_crosshairGDK_CROSSHAIR34
XC_left_ptrGDK_LEFT_PTR68
XC_plusGDK_PLUS90
XC_right_ptrGDK_RIGHT_PTR94
XC_tcrossGDK_TCROSS130
XC_xtermGDK_XTERM152
html-dir "."
html-dir is the directory to search for documentation if an HTML reader is in use. See the function html in index.scm.
html-program "firefox"
This is the program to use to read HTML files. On the Mac, you need to give the full path to the executable image: "/Applications/Safari.app/Contents/MacOS/Safari". See the function html in index.scm.
just-sounds #f
In Motif and in Gtk versions 2.3 or later, if just-sounds is #t, the file lists displayed by the file selection dialogs are filtered to show just sound files (see add-sound-file-extension).
ladspa-dir #f
LADSPA is a way of managing plug-ins in Linux. I consider it very old-fashioned, but there are a bunch of ladspa libraries, and they're easy to load. so... ladspa-dir is the name of the directory to search for LADSPA plugin libraries (it can override or replace LADSPA_PATH). See Snd and LADSPA.
log-freq-start 32.0
log-freq-start is the start (lowest) frequency used in the log freq display (ffts). Since the log display emphasizes the lower frequencies, but the lowest are all inaudible, it seemed more informative to squash the lowest 30Hz or so into a single point (0 Hz) on the log freq graphs; otherwise the audible data starts about 1/4 of the way down the x axis, wasting valuable screen space! But it also seemed a bother to have to set/reset the spectrum-start variable every time you wanted to flip between log and linear displays. log-freq-start to the rescue? For other ideas along these lines, see display-bark-fft.
max-regions 16
This sets the maximum size of the region list, the number of regions that are accessible.
max-virtual-ptrees 3
This sets the maximum number of parse-trees (see ptree-channel) that the virtual editor will allow in any virtual edit (once the maximum is reached, the operation is handled locally, not via a virtual edit).
minibuffer-history-length 8
This sets the maximum length of the minibuffer and listener M-p/M-n history lists.
mus-max-malloc 67108864
This sets the maximum memory allocation (in bytes).
mus-max-table-size 20971520
This sets the maximum table size (delay line length in samples, etc).
open-file-dialog-directory "."
open-file-dialog-directory is the name of the initial open file dialog directory (normally ".").
optimization 0
optimization affects optimization levels in Scheme. If non-zero, it causes Snd to try to optimize simple lambda forms passed to the searches and so forth. This depends partly on the optargs module, and applies only to S7 and Guile. The actual values of the optimization switch are:
    0:  no optimization (use the standard Scheme parser/evaluator)
    1:  optimize simple stuff (if complex result possible, give up)
    2:  assume nothing will return a complex number
    3:  if an undefined variable is encountered, try to guess its type
    4:  make questionable assumptions about variable types
    5:  make dangerous assumptions about variable locations (for set!)
    6:  try to splice in user-defined functions
Currently, the optimizer is able to speed up Scheme code by factors between 8 and 20; see run.c for what is implemented, what the major limitations are, and so on. If you set the optimization-hook to print out whatever its argument is, you can find out what the optimizer found confusing:
    (add-hook! optimization-hook (lambda (n) (display (format #f "opt: ~A~%" n))))
See also max-virtual-ptrees.
print-length 12
For objects such as vcts, print-length sets the number of elements printed. In S7, this also sets *vector-print-length*.
    >(set! (print-length) 3)
    3
    >(make-vct 10 .1)
    #<vct[len=10]: 0.100 0.100 0.100 ...>
run-safety 0
This is obsolete. run-safety has no effect anymore.
save-dir #f
save-dir is the name of the directory for saved-state files.

These files are written when you call save-state or choose the Options:Save session menu item. If any of the current sounds has an edit that requires saved data, it is written as a separate sound file, and that file is reloaded automatically when you restart the saved session. To keep such files safe, or at least separate from others, you can set up separate directory for them. (set! (save-dir) "/tmp").
save-state-file "saved-snd.scm"
This is the saved state file name.
selection-creates-region #t
If selection-creates-region is #t, a region is created whenever a selection is made. If you're editing very large sounds and using selections, the region temp files can use up a lot of disk space (and the time to write them); if you're not using regions anyway, this switch can turn them off.
show-backtrace #f
If show-backtrace is #t, any error will display a backtrace automatically.
show-indices #f
If show-indices is #t, each sound's name is preceded by its index in the sound pane.
show-selection-transform #f
If show-selection-transform is #t, Snd displays the transform of the current active selection, if any. The sonogram and spectrogram displays ignore this flag because they assume their time axis matches that of the time domain graph.
sinc-width 10
sinc-width is the width in samples of the sampling rate conversion sinc interpolation.

The higher this number, the better the src low-pass filter, but the slower src runs. If you use too low a setting, you can sometimes hear high frequency whistles leaking through. To hear these on purpose, make a sine wave at (say) 55 Hz, then (src-sound '(0 3 1 1)) with sinc-width at 4.
snd-version "1-Jun-06" etc
This is a string giving the current Snd version, normally a date. version is a Guile function.
temp-dir #f
temp-dir is the directory to use for temporary files; if it is #f, Snd uses whatever the system default is, usually "/tmp" or "/var/tmp". See also snd-tempnam.
trap-segfault #t
If trap-segfault is #t, Snd tries to catch segfaults and continue anyway. This normally gives you a chance to save your current work, but please also send bil@ccrma.stanford.edu a bug report!
window-height 0
window-height is the current Snd window height in pixels. This is the same as
  Scheme: (cadr (widget-size (cadr (main-widgets))))

  Ruby:   widget_size(main_widgets.cadr).cadr

  Forth:  main-widgets cadr widget-size cadr
except at startup when the window-height function and friends defer the assignment until after the main widgets have been created. If Snd becomes confused about screen size, it can make its main window so large that you can't get at any of the decorations for resizing the window; in this emergency you can (set! (window-height) 300) or some such number.
window-width 0
This is the current Snd window width in pixels.
window-x -1
This is the current Snd window left side position in pixels (-1 means unset). This is (usually) the same as
    (car (widget-position (cadr (main-widgets))))
window-y -1
This is the current Snd window upper side position in pixels (X numbering starts at 0 at the top, -1 means unset).
with-background-processes #t
with-background-processes determines whether Snd should use background (idle time) processes for ffts and so forth. It is intended primarily for auto-testing.
with-file-monitor #t
If with-file-monitor is #t (the default), the file alteration monitor is active. There are still bugs in this library that can cause Snd to hang — I haven't tracked down what the problem is yet; in the meantime, set this switch to #f to disable the monitor. (One such bug was fixed in gamin 1.8.0).
with-relative-panes #t
If with-relative-panes is #t in the Motif version of Snd, a multichannel sound tries to retain the relative channel graph sizes when the outer sash (the overall sound size sash) changes. Mono sounds and the listener are not affected (perhaps they should be?).
zoom-focus-style zoom-focus-active
This determines what a zoom action focuses (centers) on. The choices are zoom-focus-left, zoom-focus-right, zoom-focus-active, zoom-focus-middle, or a function of 6 arguments. The function should return the new window left edge as a float. Its arguments are the current sound index, channel number, zoom slider value (0.0 to 1.0), time domain window left and right edges in seconds, and the current total x axis size (seconds) corresponding to a slider value of 1.0.
    (set! (zoom-focus-style) (lambda (snd chn zx x0 x1 range) (- x1 (* zx range))))
mimics zoom-focus-right. zoom-focus-active tries to focus on some object in the view: the cursor, a mix or mark, etc. See also Zoom options.


Hooks

When some user-interface action takes place, code is called that responds to that action; these functions are sometimes called callbacks; the variable that holds a list of such callbacks is known as a hook. A hook provides a way to customize user-interface actions. The hook itself is list of functions. The function add-hook! adds a function to a hook's list, remove-hook! removes a function, and reset-hook! clears out the list. For example, the hook that is checked when you click the sound's name in the minibuffer is name-click-hook. We can cause that action to print "hi":

  Scheme: (add-hook! name-click-hook (lambda (snd) (snd-print "hi") #t))

  Ruby:   $name_click_hook.add_hook!("print") do |snd| snd_print("hi"); true end

  Forth:  name-click-hook lambda: <{ snd }> "hi" snd-print drop #t ; add-hook!

If there is more than one function attached to a hook, some of the hooks "or" the functions together (marked [or] below); that is they run through the list of functions, and if any function returns something other than #f, the hook invocation eventually returns the last such non-#f value. A few hooks are "cascade" hooks; that is, each function gets the result of the previous function, and the final function's value is returned. In the other cases ("progn", the name coming from Common Lisp), the result returned by the hook is the result of the last function in the list. Whatever the hook combination choice, all the functions on the hook list are run on each invocation. There are a variety of hook-related functions in hooks.scm.

There are several basic actions that involve a bunch of hooks. Here is a schematic view of some of these sequences.

    Open filename
        bad header?: bad-header-hook — can cancel request
        no header?:  open-raw-sound-hook — can cancel request
        file ok: 
             open-hook — can change filename
             file opened (no data read yet)
                 during-open-hook (can set prescaling etc)
             data read, no graphics yet
             after-open-hook
             initial-graph-hook
    
    
    Save current sound
        before-save-as-hook — can cancel the request or set its output parameters
        save-hook
            sound saved
              if any sample is clipped during save, clip-hook
        after-save-as-hook


    Play sound
        when a play request occurs: start-playing-hook — can cancel the request, also start-playing-selection-hook
            (any number of sounds can be playing at once)
        as each buffer is sent to the audio device: play-hook and dac-hook
        as each sound ends: stop-playing-hook, stop-playing-selection-hook
        close audio device: stop-dac-hook
    

    Close sound
        before-close-hook — can cancel close
        close-hook (sound is still open)
        sound closed
    
    
    Save current Snd ("session") state
        save-state-hook — can change output filename (crummy name is an historical artifact)
        output save-state file opened
            before-save-state-hook
            Snd saves its state
            after-save-state-hook
        output closed
    
    
    Exit Snd
        before-exit-hook — can cancel exit request
        exit-hook
        Snd cleans up and exits

You can find out what's on a given hook with the following (which is mostly adding carriage returns to the printout from hook->list):

(define (describe-hook hook)
  (for-each 
    (lambda (n) 
      (snd-print (format #f "~A~%" n)))
    (reverse (hook->list hook))))

Here's the Ruby version of some of the hook-related functions:

    $var_hook.remove_hook!("proc_name")
    $var_hook.reset_hook!
    $var_hook.run_hook do |prc| prc.call(1, 2, 3) end
    $var_hook.call(1, 2, 3)   # calls all procedures
    
    require 'hooks'
    $var_hook.show            # prints the code of the procedure(s)
    $va_hook.to_a

And some Forth examples, taken from Mike Scholz's documentation:

    open-hook ' open-buffer 1 make-proc add-hook!
    open-hook "open-buffer" remove-hook!
    open-hook reset-hook!
    open-hook hook->list
    
    2 "A simple hook." create-hook my-new-hook
    my-new-hook ' + 2 make-proc add-hook!
    my-new-hook '( 2 3 ) run-hook
    help my-new-hook             

These hooks are extremely easy to add; if there's some user-interface action you'd like to specialize in some way, send me a note. hooks.scm has snd-hooks and reset-all-hooks, as well as other useful hook-related functions.

In the following list of hooks, the arguments after the hook name refer to the arguments to the functions invoked by the hook. That is, after-apply-controls-hook (snd) means that the functions on the after-apply-controls-hook list each take one argument, a sound index. If the argument list is followed by some indication such as "[or]", that means the various hook function values are or-d together.

after-apply-controls-hook (snd)
This hook is called when apply-controls finishes. add-amp-controls in snd-motif.scm uses this hook to reset any added amplitude sliders to 1.0.
after-graph-hook (snd chn)
This hook is called after a graph is updated or redisplayed; see display-samps-in-red, draw-smpte-label in snd-motif.scm, or add-comment. This is the hook to use when adding your own finishing touches to the display; if added earlier they risk being erased by Snd as it redraws graphs.
after-lisp-graph-hook (snd chn)
This hook is called after a "lisp" graph is updated or redisplayed. The lisp-graph-hook functions are called before the actual graph is displayed, so if you want to add to a graph in some way, you need to use after-lisp-graph-hook. display-bark-fft in dsp.scm uses it to draw the x axis labels and ticks for various frequency scales.
after-open-hook (snd)
This hook is called just before a newly opened sound's window is displayed. This provides a way to set various sound-specific defaults. For example, the following causes Snd to default to locally sync'd channels (that is, each sound's channels are sync'd together but are independent of any other sound), united channels (all chans in one graph), and filled graphs (not line segments or dots, etc):
(add-hook! after-open-hook
  (lambda (snd)
    (if (> (channels snd) 1)
        (begin
          (set! (sync snd) (+ 1 snd)) ; 0 = #f
          (set! (channel-style snd) channels-combined)
          (set! (graph-style snd) graph-filled)))))
See also C-x b support in examp.scm, remember-sound-state in extensions.scm, enved.scm, and various examples in snd-motif.scm.
after-save-as-hook (index filename from-dialog)
This hook is called after File:Save as. See emacs-style-save-as in snd7.scm which closes the current sound and opens the newly created one to mimic Emacs.
after-save-state-hook (filename)
This hook is called after Snd has saved its state (save-state). 'filename' is the (otherwise complete) saved state program. See ws-save-state in ws.scm or remember-sound-state in extensions.scm. Both use this sequence:
(lambda (filename)
  (let ((fd (open filename (logior O_RDWR O_APPEND)))) ; open to write at the end
    (format fd "~%~%;;; save-state stuff here ~%")
    ...
    (close fd)))
after-transform-hook (snd chn scaler)
This hook is called just after an FFT (or spectrum) is calculated.
(define (report-fft-peak snd chn scale)
  (if (and (transform-graph?) 
           (= (transform-graph-type) graph-once))
      (report-in-minibuffer 
        (number->string (/ (* 2 (vct-peak (transform->vct snd chn))) 
                           (transform-size snd chn))))))
(add-hook! after-transform-hook report-fft-peak)
bad-header-hook (filename) [or]
This hook is called if a file has a bogus-looking header (that is, a header with what appear to be bad values such as a negative number of channels). If a hook function returns #t, Snd does not try to open the file.
    (add-hook! bad-header-hook (lambda (n) #t)) ; don't open bogus-looking files
If no header is found, open-raw-sound-hook is invoked instead ("raw" = "headerless").
before-close-hook (snd) [or]
This hook is called when a file is about to be closed. If a hook function returns #t, the file is not closed (see check-for-unsaved-edits in extensions.scm).
before-exit-hook () [or]
This hook is called upon a request to exit Snd. If a hook function returns #t, Snd does not exit. This can be used to check for unsaved edits (see above or extensions.scm: unsaved-edits?).
before-save-as-hook (index filename selection srate header-type data-format comment) [or]
This hook is called before save-sound-as or File:Save as. If a hook function returns something other than #f, the save is not performed. This hook provides a way to do last minute fixups (srate conversion for example) just before a sound is saved. The arguments to the hook function describe the requested attributes of the saved sound; 'index' is the to-be-saved sound's index; 'filename' is the output file's name; 'selection' is #t if we're saving the selection.
(add-hook! before-save-as-hook
  (lambda (index filename selection sr type format comment)
    (if (not (= sr (srate index)))
        (let ((chns (chans index)))
	  (do ((i 0 (+ 1 i)))
	      ((= i chns))
	    (src-channel (exact->inexact (/ (srate index) sr)) 0 #f index i))
	  (save-sound-as filename index :header-type type :data-format format :srate sr :comment comment) 
	  ;; hook won't be invoked recursively
	  (do ((i 0 (+ 1 i)))
	      ((= i chns))
	    (undo 1 index i))
	  #t) ; tell Snd that the sound is already saved
	#f)))
before-save-state-hook (filename) [or]
This hook is called before Snd saves its state (save-state). 'filename' is the saved state file. If the hook functions return #t, the save state file is opened in append mode (rather than create/truncate), so you can write preliminary stuff via this hook, then instruct Snd not to clobber it during the save process.
(add-hook! before-save-state-hook
  (lambda (name) 
    (with-output-to-file name
      (lambda ()
        (display (format #f ";this comment will be at the top of the saved state file.~%~%"))
	#t))))
before-transform-hook (snd chn) [progn]
This hook is called just before an FFT (or spectrum) is calculated. If a hook function returns an integer, that value is used as the starting point (sample number) of the fft. Normally, the fft starts from the left window edge. To have it start at mid-window:
(add-hook! before-transform-hook
  (lambda (snd chn)        ; 0.5 * (left + right) = midpoint
    (inexact->exact (round (* 0.5 (+ (right-sample snd chn) (left-sample snd chn)))))))
The following somewhat brute-force code shows a way to have the fft reflect the position of a moving mark:
(define fft-position #f)
(add-hook! before-transform-hook (lambda (snd chn) fft-position))
(add-hook! mark-drag-hook (lambda (id)
                            (set! fft-position (mark-sample id))
                            (update-transform-graph)))
clip-hook (clipping-value) [progn]
This hook is called whenever a sample is about to be clipped while writing out a sound file. The hook function can return the new value.
close-hook (snd)
This hook is called when a file is closed (before the actual close, so the index 'snd' is still valid).
(add-hook! close-hook
  (lambda (snd) 
    (system "sndplay wood16.wav")))
$close_hook.add_hook!("play") do |snd| 
  system("aplay wood16.wav")
end
close-hook is used in autosave.scm, the C-x b support in examp.scm, remember-sound-state in extensions.scm, the peak env support in peak-env.scm, and many other places.
color-hook () [progn]
This hook is called whenever one of the variables associated with the color dialog changes. See start-waterfall in snd-gl.scm.
dac-hook (data) [progn]
This hook is called just before data is sent to DAC; 'data' is a sound-data object. See with-level-meters in snd-motif.scm.
draw-mark-hook (id) [progn]
This hook is called before a mark is drawn (in XOR mode except in cairo). If the hook function returns #t, the mark is not drawn. mark-sync-color in snd-motif.scm uses this hook to draw sync'd marks in some other color than the current mark-color.
draw-mix-hook (id old-x old-y x y) [progn]
This hook is called before a mix tag is drawn. If the hook function returns either #t or a list, the mix tag is not drawn by Snd (the assumption is that the hook function drew something). old-x and old-y are the previous mix tag positions (in case you're using draw-mix-hook to draw your own mix tag as in musglyphs.scm). x and y give the current position. If the hook function returns a list, its two elements (integers) are treated as the mix's tag x and y locations for subsequent mouse click hit detection.
drop-hook (filename) [or]
This hook is called each time Snd receives a drag-and-drop event, passing the hook functions the dropped filename. If the hook functions return #t, the file is not opened by Snd. Normally if you drag a file icon to the menubar, Snd opens it as if you had called open-sound. If you drag the icon to a particular channel, Snd mixes it at the mouse location in that channel. To get Snd to mix the dragged file even from the menubar:
    (add-hook! drop-hook (lambda (filename) (mix filename) #t)) ; return #t = we already dealt with the drop
snd-motif.scm has examples that add a drop callback to an arbitrary widget, or change an existing callback (to pass the sound index and channel number to the drop callback function, bypassing drop-hook).
during-open-hook (fd name reason)
This hook is called after file is opened, but before data has been read. This provides an opportunity to set sndlib prescaling values:
(add-hook! during-open-hook
  (lambda (fd name reason)
    (if (= (mus-sound-header-type name) mus-raw)
        (set! (mus-file-prescaler fd) 500.0))))
The prescaling affects only sound data made up of floats or doubles. 'reason' is an integer indicating why this file is being opened:
    0: reopen a temporarily closed file (internal to Snd — normally invisible)
    1: sound-open, File:open etc — the normal path to open a sound
    2: copy reader — another internal case; this happens if a sound is played and edited at the same time
    3: insert sound (File:Insert etc)
    4: re-read after an edit (file changed, etc — an invisible editing case)
    5: open temp file after an edit (another invisible editing case)
    6: mix sound (File:Mix etc)
So, to restrict the hook action to the normal case where Snd is opening a file for the first time, check that 'reason' is 1, or perhaps 1, 3, or 6 (these read the external form of the data).
enved-hook (env pt new-x new-y reason) [cascade]
Each time a breakpoint is changed in the envelope editor, this hook is called; if it returns a list, that list defines the new envelope, otherwise the breakpoint is moved (but not beyond the neighboring breakpoint), leaving other points untouched. The kind of change that triggered the hook callback is indicated by the argument 'reason'. It can be enved-move-point, enved-delete-point, or enved-add-point. This hook makes it possible to define attack and decay portions in the envelope editor, or use functions such as stretch-envelope from env.scm:
(add-hook! enved-hook
  (lambda (env pt x y reason)
    (if (= reason enved-move-point)
        (if (and (> x 0.0) (< x (envelope-last-x env))) ; from env.scm
            (let* ((old-x (list-ref env (* pt 2)))
                   (new-env (stretch-envelope env old-x x)))
              (list-set! new-env (+ (* pt 2) 1) y)
              new-env)
            env)
        #f)))
If there are several functions on the hook, each gets the envelope result of the preceding function (if a function returns #f, the envelope is not changed). A math-type would call this a "function composition" method combination; a filter-type would say "cascade";
exit-hook ()
This hook is called upon exit. It can be used to perform cleanup activities; in peak-env.scm, for example, we save peak-env info upon exit:
    (add-hook! exit-hook (lambda () (for-each save-peak-env-info (sounds))))
For more examples, see extensions.scm and autosave.scm. Guile's exit-hook is shadowed by this variable.
graph-hook (snd chn y0 y1) [or]
This hook is called each time a graph is updated or redisplayed. If its hook functions return #t, the display is not updated. See examp.scm for many examples. If you want to add your own graphics to the display, use after-graph-hook.
(add-hook! graph-hook
  (lambda (snd chn y0 y1)
    "set the dot size depending on the number of samples being displayed"
    (let ((dots (- (right-sample snd chn) (left-sample snd chn))))
      (if (> dots 100) 
	  (set! (dot-size snd chn) 1)
	(if (> dots 50)
	    (set! (dot-size snd chn) 2)
	  (if (> dots 25)
	      (set! (dot-size snd chn) 3)
	    (set! (dot-size snd chn) 5))))
      #f)))
help-hook (subject help-string) [cascade]
This hook is called from snd-help with the current help subject and default help-string. Say we want the index.scm procedure html called any time snd-help is called (from C-? for example):
    (add-hook! help-hook (lambda (subject help) (html subject) #f))
If there is more than one hook function, each function's result is passed as input to the next function.
initial-graph-hook (snd chn dur) [or]
This hook is called the first time a given channel is displayed (when the sound is first opened). If the hook function returns a list, the list's contents are interpreted as:
    (list x0 x1 y0 y1 label ymin ymax)
(all trailing values are optional), where these numbers set the initial x and y axis limits and the x axis label. The default (an empty hook) is equivalent to:
    (add-hook! initial-graph-hook (lambda (snd chn dur) (list 0.0 0.1 -1.0 1.0 "time" -1.0 1.0)))
The 'dur' argument is the total length in seconds of the displayed portion of the channel, so to cause the entire sound to be displayed initially:
    (add-hook! initial-graph-hook (lambda (snd chn dur) (list 0.0 dur)))
To get other the data limits (rather than the default y axis limits of -1.0 to 1.0), you can use mus-sound-maxamp, but if that sound's maxamp isn't already known, it can require a long process of reading the file. The following hook procedure uses the maxamp data if it is already available or the file is short:
(add-hook! initial-graph-hook
  (lambda (snd chn dur)
    (if (or (mus-sound-maxamp-exists? (file-name snd))
            (< (frames snd chn) 10000000))
	(let* ((amp-vals (mus-sound-maxamp (file-name snd)))
	       (max-val (max 1.0 (list-ref amp-vals (+ (* chn 2) 1)))))
               ;; max amp data is list: (sample value sample value ...)
	  (list 0.0 dur (- max-val) max-val)) ; these are the new y-axis limits
	(list 0.0 dur -1.0 1.0))))            ; max amp unknown, so use defaults
A similar problem affects the 'dur' argument. If the file is very long, Snd starts a background process reading the file's data to get an overall amplitude envelope, and this envelope is what it actually displays when you zoom out to look at the entire sound. If you set 'x1' to 'dur', you effectively get two such processes contending for access to the data. One way around this is to save the envelope as a "peak envelope" in Snd's nomenclature; load peak-env.scm to make this process automatic.
key-press-hook (snd chn key state) [or]
This hook is called upon key press while the mouse is in the lisp graph (the third graph, to the right of the time and fft graphs). If its function returns #t, the key press is not passed to the main handler. 'state' refers to the control, meta, and shift keys. start-enveloping in enved.scm uses this hook to add C-g and C-. support to the channel-specific envelope editors.
lisp-graph-hook (snd chn) [progn]
This hook is called just before the lisp graph is updated or redisplayed (see display-db). If its function returns a list of pixels (xm style), these are used in order by the list of graphs (if any), rather than Snd's default set (this makes it possible to use different colors for the various graphs). If it returns a function (of no arguments), that function is called rather than the standard graph routine:
(add-hook! lisp-graph-hook
	   (lambda (snd chn)
	     (lambda ()
	       (draw-string "hi" 
			    (x->position 0.5 snd chn lisp-graph) 
			    (y->position 0.0 snd chn lisp-graph)
			    snd chn))))
For a fancy example, see display-bark-fft in dsp.scm.
listener-click-hook (textpos)
This hook is called when a click occurs in the listener; the 'textpos' argument is the position in the text (a character number) where the click occurred. See click-for-listener-help in draw.scm.
mark-click-hook (id) [progn]
This hook is called when a mark is clicked; return #t to squelch the default minibuffer mark identification. The following hook function is used in with-marked-sound in ws.scm to display arbitrary info about a mark.
(add-hook! mark-click-hook
  (lambda (n) 
    (if (not (defined? 'mark-properties)) (load "marks.scm"))
    (info-dialog "Mark Help"
      (format #f "Mark ~D~A:~%  sample: ~D = ~,3F secs~A~A"
	      n 
	      (let ((name (mark-name n)))
   	        (if (> (string-length name) 0)
		    (format #f " (~S)" name)
		    ""))
	      (mark-sample n)
	      (/ (mark-sample n) (srate (car (mark-home n))))
	      (if (not (= (mark-sync n) 0))
	        (format #f "~%  sync: ~A" (mark-sync n))
	        "")
	      (let ((props (mark-properties n)))
	        (if (and (list? props)
		         (not (null? props)))
	          (format #f "~%  properties: '~A" props)
	          ""))))
    #t))
mark-drag-hook (id)
This hook is called when a mark is dragged.
(define (report-mark-location id)
  ;; print current mark location in minibuffer
  (let ((samp (mark-sample id))
        (sndchn (mark-home id)))
    (report-in-minibuffer 
      (format #f "mark ~D: sample: ~D (~,3F) ~A[~D]: ~,3F"
              id samp 
              (/ samp (srate (car sndchn))) 
              (short-file-name (car sndchn))
              (cadr sndchn)
	      (sample samp (car sndchn) (cadr sndchn))))))

(add-hook! mark-drag-hook report-mark-location)
mark-drag-triangle-hook (id x time dragged-before) [progn]
This hook is called when a mark play triangle is dragged. The smoothness of the response to the drag motion is largely determined by dac-size. 'dragged-before' is #f when the drag starts and #t thereafter. 'x' is the mouse x location in the current graph. 'time' is the uninterpreted (graphics toolkit) time at which the drag event was reported. 'id' is the mark id. If the hook function returns #t, Snd takes no further action. To set up to play, then interpret the motion yourself, return #f on the first call, and #t thereafter:
(let ((first-x 0))
  (add-hook! mark-drag-triangle-hook
    (lambda (id x time dragged-before)
      (if (not dragged-before)
	  (set! first-x x)
	  (set! (speed-control) (/ (- x first-x) 16.0)))
      dragged-before)))
mark-hook (id snd chn reason)
This hook is called when a mark is added, deleted, or moved (but not while moving). 'id' can be -1 (i.e. no specific mark). 'reason' can be 0: add, 1: delete, 2: move (via set! mark-sample), 3: delete all marks, 4: release (after drag). In the "release" case, the hook is called upon button release before any edits (control-drag of mark) or sorting (simple drag), and if the mark-sync is not 0, the hook is called on each syncd mark.
(define (snap-mark-to-beat)
  ;; when a mark is dragged, its end position is always on a beat
  (let ((mark-release 4))
    (add-hook! mark-hook
      (lambda (mrk snd chn reason)
        (if (= reason mark-release)
            (let* ((samp (mark-sample mrk))
	           (bps (/ (beats-per-minute snd chn) 60.0))
		   (sr (srate snd))
		   (beat (floor (/ (* samp bps) sr)))
		   (lower (inexact->exact (/ (* beat sr) bps)))
		   (higher (inexact->exact (/ (* (+ 1 beat) sr) bps))))
	      (set! (mark-sample mrk)
	      (if (< (- samp lower) (- higher samp))
		  lower
 		  higher))))))))
mix-click-hook (id) [progn]
This hook is called when a mix tag is clicked; return #t to omit the default action which is to print the mix id in the minibuffer. A more informative version is mix-click-info in mix.scm. Here's an example that sets a mix's amps to 0 if you click it (see mix-click-sets-amp in mix.scm for a fancier version):
(add-hook! mix-click-hook
  (lambda (n)
    (set! (mix-amp n) 0.0)
    #t))
mix-drag-hook (id x y)
This hook is called when a mix is dragged.
(add-hook! mix-drag-hook
  (lambda (n x y) 
    (report-in-minibuffer 
      (format #f "mix ~A at ~D: ~,3F" 
        n (mix-position n) 
        (exact->inexact (/ (mix-position n) (srate)))))))
A neat example is to set up an empty sound with a 1.0 in sample 0, mix in a vct containing one element of 0.5, then set up this mix-drag-hook:
    (add-hook! mix-drag-hook (lambda (id x y) (update-transform-graph)))
and turn on the FFT graph. As you drag the mix, you can see the spectral effect of that moving value as a comb filter.
mix-release-hook (id samps) [progn]
This hook is called after a mix has been dragged by the mouse to a new position. 'id' is the mix id, 'samps' is the number of samples moved during the drag. If its function returns #t, the final position of the mix is hook's responsibility. See snap-mix-to-beat in mix.scm.
mouse-click-hook (snd chn button state x y axis) [or]
This hook is called upon a mouse button release or click (with various exceptions). If its function returns #t, the click is ignored by Snd. See the current-window-location function in draw.scm. Here's a simpler example:
(define (click-to-center snd chn button state x y axis)
  ;; if mouse click in time domain graph, set cursor as normally, but also center the window
  (if (= axis time-graph)
      (let ((samp (inexact->exact (* (srate snd) (position->x x snd chn)))))
	(set! (cursor snd chn) samp)
	(set! (right-sample snd chn) 
          (- samp (inexact->exact (* .5 (- (left-sample snd chn) (right-sample snd chn))))))
	(update-time-graph)
	#t)
      #f))
(add-hook! mouse-click-hook click-to-center)

;;; this example disables button 2 -> insert selection
(add-hook! mouse-click-hook
  (lambda (snd chn button state x y axis)
    (and (= axis time-graph) (= button 2))))
The mouse scroll wheel is sometimes reported as buttons 4 and 5; in the next example, turning the wheel zooms the graph in or out:
(add-hook! mouse-click-hook
  (lambda (snd chn button state x y axis)
    (if (and (= axis time-graph)
	     (or (= button 4) (= button 5))) ; mouse scroll wheel
	(let ((midpoint (* 0.5 (apply + (x-bounds))))
	      (dur (/ (frames) (srate)))
	      (range (if (= button 4)
			 (* -0.25 (apply - (x-bounds))) ; zoom in
			 (abs (apply - (x-bounds))))))  ; zoom out
	  (set! (x-bounds) (list (max 0.0 (- midpoint range))
				 (min dur (+ midpoint range))))))
    #f))
Here is a Forth example:
mouse-click-hook lambda: <{ snd chn button state x y axis -- }> 
 axis time-graph = if 
   $" freq: %.3f" '( snd chn #f cursor  snd chn spot-freq ) string-format 
   snd #f report-in-minibuffer 
 else 
   #f 
 then 
; add-hook! 
mouse-drag-hook (snd chn button state x y)
This hook is called when the mouse is dragged within the lisp graph (see enved.scm or rtio.scm).
mouse-enter-graph-hook (snd chn)
This hook is called when the mouse enters a channel's drawing area (graph pane).
(add-hook! mouse-enter-graph-hook
  (lambda (snd chn) 
    (snd-print (format #f "~A[~A]" (short-file-name snd) chn))))
mouse-enter-label-hook (type position label)
This hook is called when the mouse enters a file viewer or region label. The 'type' is 1 for view files list, and 2 for regions. The 'position' is the scrolled list position of the label. The label itself is 'label'. We can use the finfo procedure in examp.scm to popup file info as follows:
(add-hook! mouse-enter-label-hook
  (lambda (type position name)
    (if (not (= type 2))
        (info-dialog name (finfo name)))))
See also files-popup-info in nb.scm.
mouse-enter-listener-hook (widget)
This hook is called when the mouse enters the listener pane. This hook, along with the parallel graph hook makes it possible to set up Snd to behave internally like a window manager with pointer focus. That is, to ensure that the pane under the mouse is the one that receives keyboard input, we can define the following hook procedures:
(add-hook! mouse-enter-graph-hook
  (lambda (snd chn)
    (if (sound? snd) (focus-widget (car (channel-widgets snd chn))))))

(add-hook! mouse-enter-listener-hook
  (lambda (widget)
    (focus-widget widget)))
I much prefer this style of operation.
mouse-enter-text-hook (widget)
This hook is called when the mouse enters a text widget (this is the third of the pointer focus hooks).
(add-hook! mouse-enter-text-hook
  (lambda (w)
    (focus-widget w)))
mouse-leave-graph-hook (snd chn)
This hook is called when the mouse leaves a channel's drawing area (graph pane).
mouse-leave-label-hook (type position name)
This hook is called when the mouse exits one of the labels covered by mouse-enter-label-hook. See nb.scm.
mouse-leave-listener-hook (widget)
This hook is called when the mouse leaves the listener pane.
mouse-leave-text-hook (widget)
This hook is called when the mouse leaves a text widget.
mouse-press-hook (snd chn button state x y)
This hook is called upon a mouse button press within the lisp graph (see enved.scm). The 'x' and 'y' values are relative to the lisp graph axis (as if the raw mouse pixel position was passed through position->x and position->y).
mus-error-hook (error-type error-message) [or]
This hook is called upon mus-error. If its functions return #t, Snd ignores the error (it assumes you've handled it via the hook). This hook is used in play-sound in play.scm to flush an error message that the Snd ALSA support code generates (or used to generate). Both mus_error and mus_print run this hook; in the mus_print case, the 'type' is mus-no-error (0). You can redirect mus_print output from stderr (the default) to stdout via:
(add-hook! mus-error-hook
  (lambda (typ msg)
    (and (= typ 0)         ; it's mus_print, not mus_error
         (display msg))))  ; display returns some non-#f result, I assume
To decode the 'error-type' argument, see mus-error-type->string.
name-click-hook (snd) [or]
This hook is called when the sound name is clicked (in the label in the minibuffer region of the sound's pane). If the function returns #t, the usual highly informative minibuffer babbling is squelched.
(add-hook! name-click-hook
  (lambda (snd) ; toggle read-only
    (set! (read-only snd) (not (read-only snd)))
    #t))
new-sound-hook (filename)
This hook is called whenever a new sound file is being created. sound-let in ws.scm uses this hook to keep track of newly created temporary sounds so that it can delete them once they are no longer needed.
new-widget-hook (widget)
This hook is called each time a dialog or a new set of channel or sound widgets is created. This is used in misc.scm (paint-all) to make sure all newly created widgets have the same background pixmaps.
open-hook (filename) [or]
This hook is called before a sound file is opened. If the function returns #t, or the sound is not readable (bad header, etc) the file is not opened and any corresponding after-open-hook functions are not called. If it returns a string (a filename), that file is opened instead of the original one.
(add-hook! open-hook
  (lambda (filename)
    (if (= (mus-sound-header-type filename) mus-raw)
        ;; check for "OggS" first word, if found, translate to something Snd can read
	(if (call-with-input-file filename 
	      (lambda (fd)
		(and (char=? (read-char fd) #\O)
		     (char=? (read-char fd) #\g)
		     (char=? (read-char fd) #\g)
		     (char=? (read-char fd) #\S))))
	    (let ((aufile (string-append filename ".au")))
	      (if (file-exists? aufile) (delete-file aufile))
	      (system (format #f "ogg123 -d au -f ~A ~A" aufile filename))
	      aufile) ; now open-sound will read the new .au file
	    #f)
	#f)))
See also open-buffer in examp.scm.
open-raw-sound-hook (filename current-choices) [cascade]
This hook is called each time open-sound encounters a headerless file. Its result can be a list describing the raw file's attributes (thereby bypassing the Raw File Dialog and so on): (list chans srate data-format data-location data-length) where trailing elements can be omitted ('data-location' defaults to 0, and 'data-length' defaults to the file length in bytes). If there is more than one function on the hook list, functions after the first get the on-going list result (if any) as the 'current-choices' argument (the empty list is the default).
    (add-hook! open-raw-sound-hook (lambda (file choices) (list 1 44100 mus-lshort)))
Return '() to accept all the current raw header defaults; return #f to fallback on the Raw File Dialog. The raw header defaults are stereo, 44100 Hz, big endian short data; these values can be changed in the Raw File Dialog, by calling open-raw-sound with explicit arguments, or via mus-header-raw-defaults. If the hook function returns #t, the open-sound returns without opening.
optimization-hook (message)
This hook is called each time the optimizer hits something it can't handle; 'message' tries to give some information about the situation.
    (add-hook! optimization-hook (lambda (n) (display (format #f "~A~%" n))))
Normally, if the optimizer fails for some reason, it falls back silently on the Scheme evaluator, so the code simply runs slower. This hook gives you a way to find out why the optimizer gave up.
orientation-hook () [progn]
This hook is called whenever one of the variables associated with the orientation dialog changes. See start-waterfall in snd-gl.scm.
output-comment-hook (str) [cascade]
This hook is called in the Save-As dialog to set the default output comment value. 'str' is the current sound's comment. If there is more than one hook function, each function's result is passed as input to the next function in the list.
(add-hook! output-comment-hook
  (lambda (str)   ; append a time-stamp
    (string-append str ": written " (strftime "%a %d-%b-%Y %H:%M %Z" (localtime (current-time))))))

    ;; in Ruby: format("%s: written %s", str, Time.new.localtime.strftime("%d-%b %H:%M %Z"))
output-name-hook (current-name) [progn]
This hook is called in the New File dialog. If it returns a filename, that name is presented in the New File dialog, making it slightly easier to set default output names.
(let ((file-ctr -1))
  (add-hook! output-name-hook
    (lambda (ignored-name)
      (set! file-ctr (+ file-ctr 1))
      (format #f "~A-~D.snd"    ; make new file name based on date and file-ctr: "Jun-01-23.snd"
	(strftime "%b-%d" (localtime (current-time)))
        file-ctr))))
peak-env-hook (snd chn)
This hook is called upon completion of a new peak envelope. See make-current-window-display in draw.scm.
play-hook (samps)
This hook is called each time a buffer is about to be filled for the DAC. The buffer size is 'samps'. See enved.scm and marks.scm.
print-hook (text) [progn]
This hook is called each time some Snd-generated response ('text') is about to be appended to the listener. If the function returns some non-#f result, Snd assumes you've sent the text out yourself, as well as any needed prompt. The prompt is important! Snd uses it to find the current form to evaluate, so if your print hook function forgets to include it, you can end up with a comatose listener. To get out of this state, include the prompt by hand (i.e. type in the shell that Snd started in, ">(reset-hook! print-hook)"). This is intended to make it possible to distinguish Snd responses from user-typing, or add arbitrarily fancy prompts, but both should be handled in some other way — I should rewrite this part of Snd.
(add-hook! print-hook
  (lambda (msg) 
    (if (char=? (string-ref msg 0) #\newline)
	(snd-print msg)
	(snd-print (format #f "~A~%[~A]~%~A" ;need newline just before listener-prompt
			   msg 
			   (strftime "%d-%b %H:%M %Z" (localtime (current-time)))
			   (listener-prompt))))))
read-hook (text) [or]
This hook is called each time a line is typed into the listener (it is triggered by the carriage return). If its function returns #t, Snd assumes you've dealt with the text yourself, and does not try to evaluate it. This is intended to make it possible to read user-typing in the listener.
(define (read-listener-line prompt)
  "(read-listener-line prompt) prompts for input and returns it in Snd's listener"
  (let ((res #f))
    (add-hook! read-hook (lambda (str) (set! res str) #t))
    (reset-listener-cursor)
    (snd-print #\newline)
    (snd-print prompt)
    (do () ((or (c-g?) res)))
    (reset-hook! read-hook)
    res))
save-hook (snd name) [or]
This hook is called each time a sound ('snd') is about to be saved. If its function returns #t, the file is not saved. 'name' is #f unless the file is being saved under a new name (as in save-sound-as). See autosave.scm.
save-state-hook (temp-filename)
This hook is called each time the save-state mechanism is about to create a new temporary file to save some edit history sample data; that is, each channel's edit history data is saved in a separate temporary file, and this hook provides a way to specify the name of that file. 'temp-filename' is the temporary file name that will be used unless the hook function returns a different one (as a string). This hook provides a way to keep track of which files are used in a given saved state batch, so that later cleanup is easier to manage.
select-channel-hook (snd chn)
This hook is called when a channel is selected (after the sound has been selected). The function arguments are the sound's index and the channel number. select-channel-hook is used in play-between-marks to keep the loop bounds up-to-date.
select-sound-hook (snd)
This hook is called when a sound is selected. The argument is the about-to-be-selected sound's index.
snd-error-hook (error-message) [or]
This hook is called upon snd-error. If the listener is closed, it is also called upon any Scheme, Ruby, or Forth error. If it returns #t, Snd flushes the error (it assumes you've dealt with it via the hook).
(add-hook! snd-error-hook
  (lambda (msg) 
    (play "bong.snd") ; or if xm is loaded, (XBell (XtDisplay (cadr (main-widgets))) 10)
    #f))
snd-warning-hook (warning-message) [or]
This hook is called upon snd-warning. If it returns #t, Snd flushes the warning (it assumes you've reported it via the hook).
(define without-warnings
  (lambda (thunk)
    (define no-warning (lambda (msg) #t))
    (add-hook! snd-warning-hook no-warning)
    (thunk)
    (remove-hook! snd-warning-hook no-warning)))
start-hook (filename) [or]
This hook is called when Snd starts. If its function returns #t, Snd exits immediately. Say we are so annoyed with Snd's really very fine file browser that we want Snd to exit back to the shell if its file argument is not found (this code has to be in the ~/.snd init file):
(add-hook! start-hook
  (lambda (file)
    (if (not (file-exists? file))
	(begin
	  (display file) (display " does not exist")
	  #t))))
start-playing-hook (snd) [or]
This hook is called when a sound is about to be played. If its function returns #t, Snd does not play. We can use this hook to replace "play" with "play-selection" if the selection is active:
(add-hook! start-playing-hook
  (lambda (snd)
    (if (and (selection?)
	     (selection-member? snd))
	(begin
	  (play-selection)
	  #t) ; there's a selection so don't play the entire sound
	#f))) ; no selection, so go ahead and play it all
See also report-mark-names in marks.scm.
start-playing-selection-hook () [or]
This hook is called when the selection is about to be played. If its function returns #t, Snd does not play the selection.
stop-dac-hook ()
This hook is called when Snd stops playing and turns off the DAC, normally upon mus-audio-close. It is used by with-level-meters to start draining away the red bubble in the VU meter.
stop-playing-hook (snd)
This hook is called when a sound finishes playing. stop-playing-hook may be called more often than start-playing-hook.
stop-playing-selection-hook () [progn]
This hook is called when the selection finishes playing.
time-graph-hook (snd chn) [progn]
This hook is called just before each channel's time domain graph is updated if channel-style is channels-combined (that is, when all the channels are superimposed in one graph). If its function returns a pixel, that color is used to draw that channel's data.
(add-hook! time-graph-hook
	   (lambda (snd chn)
	     (lambda ()
	       (apply make-color (colormap-ref (colormap) (/ chn (channels snd)))))))
update-hook (snd) [or]
update-hook is called just before a sound is updated ("update" means the sound is re-read from the disk, flushing the current version; this is useful if you overwrite a sound file with some other program, while viewing it in Snd). The update process can be triggered by a variety of situations, not just by update-sound. The hook is passed the sound's index. If its function returns #t, the update is cancelled (this is not recommended!); if it returns a procedure of one argument, that procedure is called upon completion of the update operation; its argument is the (possibly different) sound index. Snd tries to maintain the index across the update, but if you change the number of channels the newly updated sound may have a different index. add-mark-pane in snd-motif.scm uses the returned procedure to make sure the mark pane is reactivated right away when a sound is updated. The basic idea is:
(add-hook! update-hook
  (lambda (snd-about-to-be-updated)
    ;; this function called just before update
    (lambda (updated-snd)
      ;; this code executed when update is complete
      (snd-print "ok!"))))
I use update-hook to make sure the y axis bounds reflect the new maxamp, if it is greater than 1.0:
(add-hook! update-hook
  (lambda (old-snd)
    (lambda (snd)
      (do ((i 0 (+ 1 i)))
	  ((= i (chans snd)))
	(let ((mx (maxamp snd i)))
	  (if (> mx 1.0)
  	      (set! (y-bounds snd i) (list (- mx) mx))
	      (if (and (> (cadr (y-bounds snd)) 1.0) ; previous (pre-update) version was > 1.0
		       (<= mx 1.0))                  ; but current is not, so reset axes
		  (set! (y-bounds snd i) (list -1.0 1.0)))))))))
Say we're editing a sound in several different windows, and want to save and re-apply any edits we may have in the other windows when we save the sound from one window (all the others then get the exploding bomb icon because their underlying data has changed):
(let ((eds #f))
  (add-hook! update-hook
	     (lambda (cursnd)     ; called before actual update -- save our edits
	       (set! eds (edit-list->function cursnd 0))
	       (lambda (newsnd)   ; called after update -- reapply our edits
		 (if eds (eds newsnd 0))))))
Actually this only works if there are just two windows on the same (mono) sound. I suppose for trickier cases, we could set up an association list with the old sound index ("cursnd" above) and its edits, but it's probably better to bind some keystroke to a function that performs the same operations.
view-files-select-hook (dialog filename)
This hook is called each time a file is selected in a View Files dialog's files list.
window-property-changed-hook (command) [or]
This hook is called when Snd sees a SND_COMMAND window property change. If its function returns #t, the command is not evaluated. This is an internal debugging hook.


Channel-specific hooks:

  edit-hook (snd chn)
  undo-hook (snd chn)
  after-edit-hook (snd chn)

These are functions that return the hooks in question associated with the specified channel. The functions on these hooks are thunks — they should take no arguments. edit-hook is called just before any attempt to edit the channel's data; if it returns #t, the edit is cancelled. So,
             Scheme: (add-hook! (edit-hook snd chn) (lambda () #t))
             Ruby:   edit_hook(snd, chn).add_hook!(\"stop-edit\") do | | true end
             Forth:  snd chn edit-hook lambda: <{ }> #t ; add-hook!

halts any attempt to edit the data; this is even more restrictive than setting the read-only flag because the latter only refuses to overwrite the current data. undo-hook is called just after any undo, redo, or revert that affects the channel. after-edit-hook is called after an edit, but before after-graph-hook (add-mark-pane in snd-motif.scm uses this hook to update a mark list after each edit so that the displayed mark positions are correct). You can use edit-hook to set up protected portions of the edit history:
(define* (protect :optional snd chn)
  "(protect :optional snd chn) disallows any edits before the current one"
  (let* ((edit-pos (edit-position snd chn))
         (hook (edit-hook snd chn)))
    (reset-hook! hook)
    (add-hook! hook 
      (lambda ()
        (let ((val (< (edit-position snd chn) edit-pos)))
          (if val (report-in-minibuffer "protected"))
          val)))))

(define* (unprotect :optional snd chn)
  "(unprotect :optional snd chn) allows edits at any edit history position"
  (reset-hook! (edit-hook snd chn)))
enved.scm uses several of these hooks to implement an envelope editor in lisp. add-mark-pane in snd-motif.scm uses them to make sure the mark list reflects the current edit history location. See also autosave.scm. It is possible for after-edit-hook to be called more often that edit-hook, or vice-versa; edit-hook may be called more than once on a given attempt to edit; if a long computation is required Snd may check edit-hook ahead of time to avoid unnecessary work.


Snd's objects

Snd presents its various data structures as a list of sounds, each with a list of channels, each with lists of edits, marks, and mixes. The sound data itself is accessed through a variety of structures and functions, each aimed at a particular kind of use. The accessors from lowest level up are: sample-readers (one sample at a time iterators) and frame-readers (a "frame" is a multichannel sample), channel-at-a-time blocks (vcts, map-channel, etc), multichannel blocks (sound-data objects, map-sound, etc), a few historical leftovers that follow the "sync" field (scale-to, etc), and finally the top-level operations such as save-sound-as (these are used in the File menu, etc). In the following sections, I'll start with the lowest level and work upwards, more or less. But before launching into sample-readers, I need to explain a few things about the following documentation.

Each sound has an associated index used to refer to it in all the functions. This arbitrary number is more or less related to the sound's position in the display of sounds (if the variable show-indices is #t, the index is displayed in front of the sound's name). In the argument lists below, 'snd' as an argument refers to the sound's index, and defaults to the currently selected sound. Similarly, 'chn' is the channel number, starting from 0, and defaults to the currently selected channel. So if there's only one sound active (say its index is 0), and it has only one channel, (cursor), (cursor 0), and (cursor 0 0) all refer to the same thing. If you want to refer to the currently selected sound explicitly, either use #f as the sound index or selected-sound.

Some functions take optional-key arguments, as in CLM. These are marked "&optional-key" below, followed by the keywords themselves. As in CLM, the keywords can be omitted.

In many cases, the 'snd', 'chn', and 'reg' arguments can be #t which means "all"; if 'snd' is #t, all sounds are included. (expand-control #t) returns a list of the current control panel expansion settings of all sounds, and (set! (transform-graph? #t #t) #t) turns on the fft display in all channels of all sounds.

When an error occurs, the function throws a tag such as 'no-such-sound, 'no-active-selection, etc. All the functions that take sound and channel args ('snd chn' below) can return the errors 'no-such-sound and 'no-such-channel; all the mix-related functions can return 'no-such-mix; all the region-related functions can return 'no-such-region; all selection-oriented functions can return 'no-active-selection. To reduce clutter, I'll omit mention of these below.


Sample-readers

The simplest data access function is sample which returns the sample at a given position in a sound's channel. This simplicity, however, comes at a price in computation: if the desired sample is not in Snd's in-core (already loaded) view of the data, it has to go get the sample, which can sometimes require that it open, read, and close a sound file. The result is that sample can bring your code to a grinding halt. There are two alternatives, leaving aside the scanning and mapping functions mentioned below. One involves keeping the buffer of data around explicitly (channel->vct), and the other involves the use of a special object known as a sample-reader. The sample-reader returns the next sample in its sound each time it is called; this kind of access is sometimes called an "enumerator" (Ruby) or perhaps "iterator" (Gtk+). The buffer approach (channel->vct in expsrc) is better if you're jumping around in the data, the sample-by-sample approach if you're treating the data as a sequence of samples. To get a sample reader, you create a reader (via make-sample-reader) giving it the start position, the sound and channel to read, and the initial read direction, then get data via read-sample (which remembers the read direction passed to make-sample-reader), or next-sample (read forward) and previous-sample (read backward); when done, you can close the reader with free-sample-reader, but it's usually not necessary; the garbage collector will take care of it if you forget (but, sigh, the GC can be dilatory at times).

There is a similar set of functions giving access to the mix data. make-mix-sample-reader returns a mix reader for the desired mix, mix-sample-reader? returns #t if its argument in a mix sample reader, and read-mix-sample returns the next sample (before it is mixed into the output).

Multichannel iterations can be handled via frame-readers. These are currently implemented in frame.scm.

copy-sample-reader (obj)

copy-sample-reader returns a copy of 'obj' which can be any kind of sample-reader.
free-sample-reader (obj)
free-sample-reader releases the sample-reader 'obj'. In most cases, you don't need to call this function because the garbage collector handles the sample-reader object, but it doesn't hurt anything (but don't try to use a sample-reader after you've freed it!). If you're using zillions of sample-readers in an optimized (run-based) loop that doesn't trigger garbage collection, freeing the sample readers explicitly can reduce demands on memory.
make-mix-sample-reader (mix :optional (beg 0))
make-mix-sample-reader creates a mix-sample-reader reading 'mix' starting (in the mix input) at 'beg'. See mix->vct in mix.scm.
make-region-sample-reader (:optional start reg chn (dir 1))
make-region-sample-reader creates a sample-reader reading channel 'chn' of the region 'reg' starting at sample 'start', and reading forward if 'dir' is 1, backwards if 'dir' is -1. It is not safe to assume that this reader will return zeros beyond the region boundaries.
make-sample-reader (:optional start snd chn dir edpos)
make-sample-reader creates a sample-reader reading the given channel starting at sample 'start' with initial read direction 'dir' (1=forward, -1=backward). 'edpos' is the edit history position to read; it defaults to the current edit.
    :(open-sound "oboe.snd")
    0
    :(define reader (make-sample-reader 1000))
    #<unspecified>
    :reader
    #<sample-reader: oboe.snd[0: 0] from 1000, at 1000>
    :(read-sample reader)
    0.0328369140625
    :(sample 1000)
    0.0328369140625
    :(next-sample reader)
    0.0347900390625
    :(sample 1001)
    0.0347900390625
    :(sample-reader-home reader)
    (0 0)
    :(sample-reader-position reader)
    1002
One use of 'edpos' is to get the difference between two edits:
(define snd-diff
  (lambda () ;assume mono, get diff between current state and previous
    (let* ((index (selected-sound))
           (edit-pos (edit-position index))
           (previous-edit (make-sample-reader 0 0 index 1 (- edit-pos 1))))
      (lambda (x)
        (- x (read-sample previous-edit)) #f))))

(map-channel (snd-diff))
Once the reader has been set up to read at a given edit position, subsequent edits won't affect it. One sequence that takes advantage of this is: make-sample-reader, scale-by 0, then run an overlap-add process on the data from before the scaling.

'snd' can also be a filename (a string); in this way a sample-reader can read external sounds without going to the trouble of loading them into Snd.
    (define reader (make-sample-reader 100 "oboe.snd"))
make-sample-reader is probably the most useful function in Snd; there are lots of examples in the Scheme, Ruby, and Forth files.
mix-sample-reader? (obj)
mix-sample-reader? returns #t if 'obj' is a mix-sample-reader.
next-sample (obj)
next-sample returns the next sample (reading forward) read by the sample-reader 'obj'.
previous-sample (obj)
previous-sample returns the previous sample in the stream read by the sample-reader 'obj'.
read-mix-sample (obj)
read-mix-sample returns the next sample read by the mix-sample-reader 'obj'.
read-region-sample (obj)
read-region-sample returns the next sample read by the region-sample-reader 'obj'.
(define* (region->vct reg :optional (chn 0))
  (if (region? reg)
      (if (< chn (region-chans reg))
	  (let* ((reader (make-region-sample-reader 0 reg chn))
		 (len (region-frames reg))
		 (data (make-vct len)))
	    (do ((i 0 (+ 1 i)))
		((= i len) data)
	      (vct-set! data i (reader))))
	  (throw 'no-such-channel (list "region->vct" reg chn)))
      (throw 'no-such-region (list "region->vct" reg))))
read-sample (obj)
read-sample returns the next sample read by the sample-reader 'obj', reading in the direction set by make-sample-reader.
region-sample-reader? (obj)
region-sample-reader? returns #t if 'obj' is a region sample-reader.
sample-reader-at-end? (obj)
sample-reader-at-end? returns #t if the sample-reader 'obj' (any kind of reader) is at the end of the sound (or whatever it is reading), and hence is returning 0.0 each time it is called. When the last "real" sample is returned, the at-end? flag is still false; when it becomes true, the sample-reader returns a 0.0 sample. See locate-zero in examp.scm, or linear-src-channel in dsp.scm.
sample-reader-home (obj)
sample-reader-home returns information describing the source of the data the sample-reader 'obj' is reading. if 'obj' is a sound sample reader, it returns a list with the sound index and channel number associated with 'obj'. If 'obj' is a mix reader, it returns the mix's id. Finally, if 'obj' is a region reader, it returns a list with the region id.
sample-reader-position (obj)
sample-reader-position returns the current (sample-wise) location of the sample-reader 'obj' (any kind of reader).
sample-reader? (obj)
sample-reader? returns #t if 'obj' is a sample-reader.

If your extension language supports it, the read-sample functions can be omitted: (reader) is the same as (read-sample reader).


Snd->sample

There is a Snd-specific CLM-style generator that redirects CLM instrument input (via in-any, ina, etc) to Snd data, snd->sample.

make-snd->sample (:optional snd)

make-snd->sample creates a Snd data reader for use with CLM's in-any, file->sample, etc.
snd->sample (gen frame :optional chan)

snd->sample gets the next sample from the data accessed by 'gen', similar to file->sample. If *reverb* is a snd->sample generator, for example, ina and file->sample actually call snd->sample.
snd->sample? (obj)

snd->sample? returns #t if 'obj' is a snd->sample generator.

Vcts

Many of the Snd and CLM functions handle vectors (arrays) of data. By defining a new vector type, named vct, and providing a package of old-style array-processing calls upon that type, we can speed up many operations by a factor of 30 — enough of a difference to warrant the added complexity. make-vct creates a new vct object. It is freed by the garbage collector when it can't be referenced any further. To get an element of a vct, use vct-ref; similarly vct-set! sets an element. Once created, a vct can be passed to a variety of built-in functions:

Scheme:
  (define hi (make-vct 100))
  (vct-fill! hi 3.14)
  (vct-scale! hi -1.0)
Ruby: 
      hi = make_vct(100)
      vct_fill!(hi, 3.14)
      vct_scale!(hi, -1.0)
Forth: variable hi
       100 0.0 make-vct hi !
       hi @ 3.14 vct-fill!
       hi @ -1.0 vct-scale!

Now our vct 'hi' has 100 -3.14's.

list->vct (lst)
return a new vct with elements of list 'lst' (equivalent to the vct function).
    :(list->vct (list 0.1 0.2 0.3))
    #<vct[len=3]: 0.100 0.200 0.300>
    :(vct 0.1 0.2 0.3)
    #<vct[len=3]: 0.100 0.200 0.300>
make-vct (len (initial-element 0.0))
make-vct creates a vct of size 'len'.
    :(make-vct 3 0.1)
    #<vct[len=3]: 0.100 0.100 0.100>
sound-data->vct (sdobj (chan 0) (v #f))
sound-data->vct places the sound-data data in a vct, returning 'v' (if given) or a new vct.
vct args...
vct is equivalent to list->vct with 'args' as the list: (vct 1 2 3) is the same as (list->vct '(1 2 3))
vct? (v)
vct? returns #t if 'v' is a vct.
vct-add! (v1 v2 (off 0))
vct-add! performs element-wise add: v1[i + off] += v2[i], returning 'v1'. To protect the original vct, use vct-copy: (vct-add! (vct-vopy v1) v2).
:(define v1 (vct .1 .2 .3))
#<unspecified>
:(vct-add! v1 (vct .3 .2 .1))
#<vct[len=3]: 0.400 0.400 0.400>
:v1
#<vct[len=3]: 0.400 0.400 0.400>
vct-copy (v)
vct-copy returns a copy of the vct 'v'.

copy file: in Scheme: copy-file, in Ruby: File.copy or File.syscopy
copy string: in Guile or Forth: string-copy
copy list: in Guile or Forth: list-copy or copy-tree
copy vct: vct-copy, vct->vector, vct->frame
copy mix: mix->vct
copy sample reader: copy-sample-reader
copy (clone) current sound edit state: clone-sound-as
copy channel data: channel->vct, or save-sound-as
copy selection data: selection->vct or save-selection
copy region data: region->vct, region->vct, save-region, or region->sound-data
copy transform data: transform->vct
copy sound-data: sound-data->vct
copy a frame: frame-copy, frame->vct
copy vector: vector->vct
copy random state: Guile: copy-random-state, or use mus-rand-seed

vct-fill! (v val)
vct-fill! sets each element of 'v' to 'val': v[i] = val. It returns 'v'.
vct-length (v)
vct-length returns the length of 'v'.
vct-map! (v proc)
vct-map! sets each element of 'v' to the value returned by the thunk 'proc': (vct-map! v (lambda () 3.0)) is the same as (vct-fill! v 3.0).
vct-move! (v new old :optional backwards)
vct-move moves a block of values within a vct: v[new++] = v[old++], or if 'backwards' is #t: v[new--] = v[old--]. It returns 'v'.
:(define v1 (vct 1 2 3 4 5 6 7 8 9 10))
#<unspecified>
:(vct-move! v1 0 5)
#<vct[len=10]: 6.000 7.000 8.000 9.000 10.000 6.000 7.000 8.000 9.000 10.000>
We can use vct-move! to maintain a sliding window on a sound:
    (map-channel
      (let* ((size 100)
	     (samps (make-vct size)))
        (lambda (y)
         (vct-move! samps 0 1)
         (vct-set! samps (- size 1) y)
         ...)))
vct-multiply! (v1 v2)
vct-multiply! performs element-wise multiply of two vcts: v1[i] *= v2[i]. It returns 'v1'.
vct-offset! (v val)
vct-offset! adds 'val' to each element of 'v': v[i] += val. It returns 'v'.
vct-peak (v)
vct-peak returns the maximum absolute value of the elements of 'v'.
vct-ref (v pos)
vct-ref returns the element 'pos' in 'v': v[pos].
:(vct-ref (vct .1 .2 .3) 1)
0.200000002980232
vct-reverse! (v :optional size)
vct-reverse! reverses the elements of 'v' (in-place), returning 'v'. If 'size' is given, the reversal centers around it.
vct-scale! (v scl)
vct-scale! multiplies each element of 'v' by 'scl': v[i] *= scl. It returns 'v'.
vct-set! (v pos val)
vct-set! sets the vct 'v' element at 'pos' to 'val': v[pos] = val. In Scheme, this is the same as (set! (vct-ref v pos) val).
vct-subtract! (v1 v2)
vct-subtract! performs an element-wise subtract: v1[i] -= v2[i]. It returns 'v1'.
vct-subseq (v start :optional (end len) nv)
vct-subseq returns a new vct (or 'nv' if given) with the elements of 'v' between 'start' and 'end' inclusive. 'end' defaults to the end of 'v'.
:(define v1 (vct 1 2 3 4 5 6 7 8 9 10))
#<unspecified>
:(vct-subseq v1 3 6)
#<vct[len=4]: 4.000 5.000 6.000 7.000>
vct+ (obj1 obj2)
vct+ combines vct-add! and vct-offset!, depending on the type of its arguments. (vct+ v 1.0) or (vct+ 1.0 v) is the same as (vct-offset! v 1.0), and (vct+ v1 v2) is the same as (vct-add! v1 v2).
vct* (obj1 obj2)
vct* combines vct-multiply! and vct-scale!, depending on the type of its arguments. (vct* v 2.0) or (vct* 2.0 v) is the same as (vct-scale! v 2.0), and (vct* v1 v2) is the same as (vct-multiply! v1 v2).
vct->channel (v :optional (beg 0) dur snd chn edpos origin)
vct->channel sets the samples from 'beg' to 'beg' + 'dur' from the values in 'v'. This changes (edits) the channel, so 'origin' provides a way to name the edit (for the edit history list and whatnot). Since with-sound can write its output to a vct, we can use it with vct->channel to paste in an fm-violin note as an edit:

  (vct->channel (with-sound (:output (make-vct 44100)) (fm-violin 0 1 440 .1)))
To add such a note to whatever is already present, use either mix-vct or mix-sound-data.
vct->list (v)
vct->list returns a list with elements of 'v'.
    ;; Scheme:
    :(vct->list (list->vct (list 0.1 0.2 0.3)))
    (0.100000001490116 0.200000002980232 0.300000011920929) ; (sigh...)

    ;; Ruby:
    :vct2list(list2vct([0.1, 0.2, 0.3]))
    [0.100000001490116, 0.200000002980232, 0.300000011920929]

    ;; Forth:
    snd> '( 0.1 0.2 0.3 ) list->vct vct->list
    '( 0.1 0.2 0.3 )
vct->sound-data (v sd :optional (chan 0))
vct->sound-data places the data in vct 'v' in the sound-data object 'sd', returning sd.
vct->string (v)
vct->string returns a Scheme-readable string describing 'v'. The standard way of displaying a vct uses "#<vct...>" which is not useful if you want to read the string later as a piece of Scheme code.
    :(define v1 (make-vct 3 0.1))
    prints v1 as #<vct[len=3]: 0.100 0.100 0.100>
    :(vct->string v1)
    "(vct 0.100 0.100 0.100)"
vct->vector (v)
vct->vector returns a vector with the elements of 'v'.
vector->vct (vect)
vector->vct returns a vct with elements of vector 'vect'.

Many of the functions described below can take a vct as an argument; there are also several functions that create and fill vcts with data:

    region->vct              region samples to vct
    transform->vct           fft data to vct
    channel->vct             channel samples to vct
    mix->vct                 mix samples to vct
    sound-data->vct          sound-data samples to vct
    vector->vct              vector data to vct (assumed to be floats)
    selection->vct           selected samples to vct
    file->vct                file data to vct
    frame->vct               frame data to vct
    vct->frame               vct data to frame

We could combine some of these vct functions to implement Horner's rule (the polynomial generator) over an entire channel:

(define (vct-polynomial v coeffs)
  (let* ((v-len (vct-length v))
	 (num-coeffs (vct-length coeffs))
	 (new-v (make-vct v-len (vct-ref coeffs (- num-coeffs 1)))))
    (do ((i (- num-coeffs 2) (- i 1)))
	((< i 0))
      (vct-offset! (vct-multiply! new-v v) (vct-ref coeffs i)))
    new-v))

(define* (channel-polynomial coeffs :optional snd chn)
  (let ((len (frames snd chn)))
    (vct->channel (vct-polynomial (channel->vct 0 len snd chn) coeffs) 0 len snd chn)))

;;; (channel-polynomial (vct 0.0 .5)) = x*.5
;;; (channel-polynomial (vct 0.0 1.0 1.0 1.0)) = x*x*x + x*x + x

There is one slightly unusual function in this family: vct-map!. This is a do-loop (or for-each) over a vct, calling some function to get the values to assign into the vct. For example

  (vct-map! out-data (lambda () 
                       (convolve cnv (lambda (dir) 
                                       (read-sample sf)))))

in the cnvtest function in examp.scm is calling the convolve generator and assigning the result to each successive member of the out-data vct.

In some cases (S7, Ruby, Guile 1.4.1 or later) it's possible to access a vct's elements with the syntax (v index), equivalent to (vct-ref v index). In Ruby, vcts partake in the Enumerable and Comparable classes, and have a variety of additional methods: map, each, <=>, etc. See vct.c and the Ruby documentation for a complete list.

    :v1
    #<vct[len=10]: 0.100 0.100 0.100 3.000 0.100 0.100 0.100 0.100 0.100 0.100>
    :v1.find_all {|x| x > 1.0 }
    [3.0, 4.0]
    :v1.max
    4.0
    :v2 = make_vct(10, 0.1)
    #<vct[len=10]: 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100>
    :v2 < v1
    true


Sound-data

Another Snd object containing sound samples is the sound-data object; it can be viewed either as an array of vcts, each vct representing one channel's data, or as an array of frames, each frame holding a sample from each channel at that position in the sound. frame.scm has a number of useful functions that use sound-data objects. As with vcts, sound-data objects partake in the Enumerable and Comparable classes in Ruby.

make-sound-data (chans frames)

make-sound-data returns a new sound-data object with 'chans' arrays, each with 'frames' elements.
    :(make-sound-data 2 5)
    #<sound-data[chans=2, length=5]:
        (0.000 0.000 0.000 0.000 0.000)
        (0.000 0.000 0.000 0.000 0.000)>
sound-data-add! (sd1 sd2)

sound-data-add! adds the contents of sd2 into sd1 and returns sd1.
    :(define sd1 (make-sound-data 2 5))
    #<unspecified>
    :(do ((chn 0 (+ 1 chn))) ((= chn 2)) 
       (do ((i 0 (+ 1 i))) ((= i 5)) 
         (sound-data-set! sd1 chn i (+ i (* 5 chn)))))
    #<unspecified>
    :sd1
    #<sound-data[chans=2, length=5]:
        (0.000 1.000 2.000 3.000 4.000)
        (5.000 6.000 7.000 8.000 9.000)>
    :(sound-data-add! (sound-data-fill! (make-sound-data 2 5) .25) sd1)
    #<sound-data[chans=2, length=5]:
        (0.250 1.250 2.250 3.250 4.250)
        (5.250 6.250 7.250 8.250 9.250)>
sound-data-chans (sd)
sound-data-chans returns the number of channels in sd.
sound-data-copy (sd)
sound-data-copy returns a copy of the sound-data object sd.
sound-data-fill! (sd value)
sound-data-fill! sets each element of sound-data object sd to 'value'.
sound-data-length (sd)
sound-data-length returns the length (in samples) of each channel in the sound-data-object sd.
sound-data-maxamp (sd)
sound-data-maxamp returns a list of maxamps (one for each channel) of the data in sd.
    :sd1
    #<sound-data[chans=2, length=5]:
        (0.000 1.000 2.000 3.000 4.000)
        (5.000 6.000 7.000 8.000 9.000)>
    :(sound-data-maxamp sd1)
    (4.0 9.0)
    :(sound-data-peak sd1)
    9.0
sound-data-multiply! (sd1 sd2)
sound-data-multiply! multiplies each element of sd2 by the corresponding element of sd2 and returns sd1.
sound-data-offset! (sd off)
sound-data-offset! adds the number 'off' to each element of sd and returns sd.
sound-data-peak (sd)
sound-data-peak returns the overall maximum element in sd.
sound-data-ref (sd chan frame)
sound-data-ref returns (as a float) the sample in channel chan at location frame.
sound-data-reverse! (sd)
sound-data-reverse! reverses the elements of each channel of sd and returns sd.
    :sd1
    #<sound-data[chans=2, length=5]:
        (0.000 1.000 2.000 3.000 4.000)
        (5.000 6.000 7.000 8.000 9.000)>
    :(sound-data-reverse! sd1)
    #<sound-data[chans=2, length=5]:
        (4.000 3.000 2.000 1.000 0.000)
        (9.000 8.000 7.000 6.000 5.000)>
sound-data-scale! (sd scaler)
sound-data-scale! multiplies each sample in sd by scaler.
sound-data-set! (sd chan frame val)
sound-data-set! sets sd's sample at 'frame' in channel 'chan' to val.
sound-data+ (val1 val2)
sound-data+ adds val1 to val2 (either or both can be sound-data objects).
sound-data* (val1 val2)
sound-data* multiplies val1 by val2 (either or both can be sound-data objects).
sound-data? (obj)
sound-data? returns #t if obj is a sound-data object.
sound-data->sound-data (sd-in sd-out beg dur cycle)
sound-data->sound-data copies sound-data sd-in's data from 0 for dur frames into sound-data sd-out starting at beg, wrapping around if sd-out's end is reached. This is an experimental function currently used in the oscilloscope (oscope.scm).
sound-data->vct (sd chan v)
sound-data->vct copies sound-data sd's channel 'chan' data into vct v.
    :sd1
    #<sound-data[chans=2, length=5]:
        (4.000 3.000 2.000 1.000 0.000)
        (9.000 8.000 7.000 6.000 5.000)>
    :(sound-data->vct sd1 1)
    #<vct[len=5]: 9.000 8.000 7.000 6.000 5.000>
vct->sound-data (v sd chan)
vct->sound-data copies vct v's data into sound-data sd's channel chan.

Sndlib

All of the underlying sound library (Sndlib) functions are available, as well as most of CLM (sndclm.html). For many eamples, see play.scm and rtio.scm. The most important Sndlib functions for Snd are:

mus-alsa-buffers ()

mus-alsa-buffers is the number of buffers ("periods") used in ALSA; you can also use the environment variable MUS_ALSA_BUFFERS. The default setting is 3. These ALSA variables only matter if you built Snd with the configure switch --with-alsa.
mus-alsa-buffer-size ()

mus-alsa-buffer-size is the buffer size used in ALSA. You can also use the environment variable MUS_ALSA_BUFFER_SIZE. The defaut setting is 1024.
mus-alsa-device ()

This is the ALSA audio device; it defaults to "default". The matching environment variable is MUS_ALSA_DEVICE. If the ALSA "default" device can't be found, we also look for "plughw:0" and "hw:0". The "0" is apparently a card number or something. On my machine where the internal sound card is worse than useless, I have an EMI 2|6 connected to a USB port. Its ALSA name seems to be "hw:1" on a good day.
mus-alsa-capture-device ()

This is the ALSA capture (audio recording) device. The matching environment variable is MUS_ALSA_CAPTURE_DEVICE.
mus-alsa-playback-device ()

This is the ALSA audio playback device. The matching environment variable is MUS_ALSA_PLAYBACK_DEVICE.
mus-alsa-squelch-warning ()

Set mus-alsa-squelch-warning to #t to squelch warnings from ALSA about srate mismatches.
mus-audio-close (line)

mus-audio-close closes the audio port 'line' ('line' comes from either mus-audio-open-input or mus-audio-open-output).
(let* ((audio-fd (mus-audio-open-output mus-audio-default 22050 1 mus-lshort 1024))
       ;; open DAC at srate 22050, 1 channel, 1024 bytes per buffer
       (osc (make-oscil 440.0))                       ; make a sine wave at 440 Hz
       (data (make-sound-data 1 512)))                ; output buffer (1 chan, 512 samples)
  (do ((beg 0 (+ beg 512)))                           ; fill a buffer at a time
      ((> beg 22050))                                 ;   stop playing after 1 second
    (do ((i 0 (+ 1 i)))                                ; write each sample of the sinusoid
	((= i 512))                                   ;   until this buffer is full
      (sound-data-set! data 0 i (* .1 (oscil osc))))  ;   .1 amp
    (mus-audio-write audio-fd data 512))              ; send a buffer to the DAC
  (mus-audio-close audio-fd))                         ; close the DAC
mus-audio-describe ()
mus-audio-describe displays a string purporting to describe the audio hardware state. This function works best in OSS. If you're not getting any sound during playback, the first thing to do is to examine this function's output.
mus-audio-mixer-read (device field channel vals)
mus-audio-mixer-read reads the current state of the field 'field' of audio device 'device'. 'vals' should be a vct big enough to handle the returned data (32 is a good first guess). The 'channel' argument is often treated as the size of the vct 'vals'. Device names are listed below under mus-audio-open-input. The fields are:

  mus-audio-amp         mus-audio-srate    mus-audio-channel mus-audio-format
  mus-audio-imix        mus-audio-igain    mus-audio-reclev  mus-audio-pcm 
  mus-audio-ogain       mus-audio-line     mus-audio-synth   mus-audio-bass
  mus-audio-direction   mus-audio-port     mus-audio-pcm2    mus-audio-treble
  mus-audio-samples-per-channel
These fields originally referred to OSS soundcard settings; many years have passed, and the whole notion is now obsolete, but if you think there's some easy way to reflect the same idea in modern hardware, check out ALSA or the new Mac audio system. I can't face this mess!
mus-audio-mixer-write (device field channel vals)
mus-audio-mixer-write sets the state of the field 'field' of audio device 'device'.
;;; here we get the microphone volume, then set it to .5
(define vals (make-vct 32))
(mus-audio-mixer-read mus-audio-microphone mus-audio-amp 0 vals)
(vct-ref vals 0)
(vct-set! vals 0 .5)
(mus-audio-mixer-write mus-audio-microphone mus-audio-amp 0 vals)
;;; now set the 2 "igain" fields to 1
(vct-set! vals 0 1.0)
(vct-set! vals 1 1.0)
(mus-audio-mixer-write mus-audio-mixer mus-audio-igain 2 vals)
mus-audio-open-input (device srate chans format bufsize)
mus-audio-open-input opens the audio input (recording) port 'device'. It returns -1 if the open failed. 'device' is one of:

  mus-audio-default      mus-audio-duplex-default   mus-audio-line-out
  mus-audio-microphone   mus-audio-speakers         mus-audio-dac-out    
  mus-audio-aes-in       mus-audio-digital-in       mus-audio-digital-out
  mus-audio-aes-out      mus-audio-dac-filter       mus-audio-mixer
  mus-audio-line2        mus-audio-line3            mus-audio-aux-input 
  mus-audio-line-in      mus-audio-aux-output       mus-audio-adat-in
  mus-audio-adat-out     mus-audio-line1            mus-audio-cd 
  mus-audio-spdif-in     mus-audio-spdif-out
mus-audio-open-output (device srate chans format bufsize)
mus-audio-open-output opens the audio output (playback) port 'device'. It returns -1 if the open failed.
mus-audio-read (line sd frames)
mus-audio-read reads 'frames' frames of data into the sound-data object 'sd' from the audio input port 'line'.
mus-audio-report ()
mus-audio-report returns a string describing the current audio hardware state. mus-audio-describe prints out the same string.
mus-audio-write (line sd frames)
mus-audio-write writes 'frames' frames of data from the sound-data object 'sd' to the audio output port 'line'.
mus-bytes-per-sample (data-format)
mus-bytes-per-sample returns the number of bytes that 'data-format' uses to encode one sample of sound.
    :(mus-bytes-per-sample mus-bdouble)
    8
mus-clipping ()
mus-clipping is the default low-level clipping choice while accessing sound data. Its default is #f which makes clipping very obvious (it will cause wrap-around). If you're using the standard Snd file accessors, you probably want to use clipping, not this function. This function refers to mus-sound-open-output and friends; the file-local version is mus-file-clipping — surely we could make this more confusing! See also clip-hook.
mus-data-format-name (format)
mus-data-format-name converts 'format' from an integer to an explanatory string, e.g. "16-bit big endian linear". The sndlib data formats are:
    mus-bshort   mus-lshort   mus-mulaw   mus-alaw   mus-byte  
    mus-lfloat   mus-bint     mus-lint    mus-b24int mus-l24int
    mus-ubshort  mus-ulshort  mus-ubyte   mus-bfloat mus-bdouble 
    mus-ldouble  mus-unknown
There are also "unscaled" versions of the floating point types, and "normalized" versions of the integers.
mus-data-format->string (format)
mus-data-format->string converts 'format' from an integer to a string, e.g. "mus-mulaw".
    :(mus-data-format->string mus-bdouble)
    "mus-bdouble"
mus-error-type->string (error)
mus-error-type->string returns a brief string description of 'error' (a mus-error return type). This is only useful in mus-error-hook, and it's not very useful even there.
mus-expand-filename (name)
mus-expand-filename fills out the filename 'name' to include its 'absolute' pathname; that is, it replaces '~' with the current home directory, and whatever else seems appropriate.
    :(mus-expand-filename "oboe.snd")
    "/home/bil/cl/oboe.snd"
mus-file-clipping (fd)
This is the clipping choice for the file referred to by 'fd' (a file identifier as returned by mus-sound-open-input). The default is #f which makes clipping very obvious (it will cause wrap-around). See also clip-hook.
mus-file-prescaler (fd)
This is the prescaling value for reading data from the sndlib file descriptor 'fd'. If you're reading float data that is extremely soft (i.e. peak amplitude below .001), the transfer to integer form in sndlib (if you're using integer internal sample format) can cause bits to be lost, resulting in hiss. In this case set the prescaler for the file to 1000.0 or so to get the data into a more normal range. Since mus-file-prescaler must be set after opening the sound file, but before trying to read any data, you need to use it in the context of during-open-hook. The default prescaler value is mus-prescaler, normally 1.0.
mus-header-raw-defaults ()
mus-header-raw-defaults returns a list: '(srate chans data-format), the current raw header defaults. These can be set:
    (set! (mus-header-raw-defaults) (list 22050 4 mus-lint))
mus-header-type-name (type)
mus-header-type-name converts 'type', an integer, to a string, e.g. "AIFF". Some of the sndlib header types are:

    mus-next  mus-aifc  mus-riff  mus-rf64 mus-nist  mus-raw  mus-ircam  mus-aiff mus-caff
    mus-bicsf mus-soundfont mus-voc mus-svx mus-unsupported
This function doesn't set the header choices for output of recording; it simply decodes a sndlib header type identifier.
    :(mus-header-type-name (mus-sound-header-type "oboe.snd"))
    "Sun/Next"
The default sound output header choice is default-output-header-type, a sound file's header type is mus-sound-header-type, the CLM (with-sound) header default is *clm-header-type*, and an opened sound's header type is header-type.
mus-header-type->string (type)
mus-header-type->string converts 'type', an integer, to a string, e.g. "mus-aifc".
mus-oss-set-buffers (num size)
In Linux (OSS), this sets the number and size of the OSS fragments. The default (as of 21-May-01) is to accept whatever OSS chooses: I believe this is normally equivalent to (mus-oss-set-buffers 16 12). This default makes the control panel controls very sluggish. Snd used to call (mus-oss-set-buffers 4 12) as its default, but this seems to cause trouble for a variety of new sound cards. My initialization file includes (mus-oss-set-buffers 2 12).
mus-prescaler ()
mus-prescaler is the global default prescaling value for reading data from a file (see mus-file-prescaler).
mus-sound-chans (filename)
This is the number of channels in 'filename'. This value can be set (as can the others like it mentioned below); the assignment refers to the table of sound file data maintained by sndlib. The file itself is not touched, but any subsequent reference to it in Snd will assume the new value. In the mus-sound-chans case, say we have a sound file whose header claims it has 43 channels, but we know it only has 2:
    (set! (mus-sound-chans "43chans.snd") 2)
tells Snd that it has 2 channels no matter what the header says.
mus-sound-close-input (fd)
This closes the sound file referred to by 'fd'; 'fd' is an integer returned by mus-sound-open-input. These mus-sound-* file functions refer to a direct path to read and write sound files. Any such operation is beneath the notice, so to speak, of Snd. This function reads the first 32 samples of a file, returning the 30th in channel 0:
(define read-sample-30 
  (lambda (file)
    (let* ((fd (>mus-sound-open-input file))
	   (chans (mus-sound-chans file))
	   (data (make-sound-data chans 32)))
      (mus-sound-read fd 0 31 chans data)
      (let ((val (sound-data-ref data 0 29)))
	(mus-sound-close-input fd)
	val))))
mus-sound-close-output (fd bytes)
This function closes the sound file 'fd', and updates its length indication, if any to be 'bytes' bytes. If you didn't specify a data-format to mus-sound-open-output, it defaulted to mus-out-format. mus-out-format can currently be either ints, floats, or doubles, depending on how you've configured Snd, so it's safest to use (mus-bytes-per-sample mus-out-format) for the number of bytes per sample.
mus-sound-comment (filename)
mus-sound-comment returns the comment in the header of the file 'filename'.
    :(with-sound (:comment "this is a comment") (fm-violin 0 1 440 .1))
    "test.snd"
    :(mus-sound-comment "test.snd")
    "this is a comment"
mus-sound-data-format (filename)
mus-sound-data-format returns the data format (e.g. mus-bshort) of the file 'filename'.
    :(mus-data-format->string (mus-sound-data-format "oboe.snd"))
    "mus-bshort"
mus-sound-data-location (filename)
This is the location in bytes of the first sample in the file 'filename'.
mus-sound-datum-size (filename)
This returns the size in bytes of each sample in 'filename'. It is equivalent to (mus-bytes-per-sample (mus-sound-data-format filename)).
mus-sound-duration (filename)
This returns the duration in seconds of the sound data in the file 'filename'.
    :(mus-sound-duration "oboe.snd")
    2.30512475967407
mus-sound-forget (filename)
mus-sound-forget removes the file 'filename' from the sndlib sound cache.
mus-sound-frames (filename)
mus-sound-frames returns the number of frames of sound data in the file 'filename' according to its header (this number is occasionally incorrect in mus-next headers).
mus-sound-header-type (filename)
This returns the header type (e.g. mus-aifc) of the file 'filename'.
    :(mus-header-type->string (mus-sound-header-type "oboe.snd"))
    "mus-next"
mus-sound-length (filename)
mus-sound-length returns the number of bytes of sound data in the file 'filename'.
mus-sound-loop-info (filename)
This function refers to the "loop" info that is sometimes found in some headers (aifc, wav etc). mark-loops in examp.scm uses mus-sound-loop-info to place a mark at each loop point.
    :(mus-sound-loop-info "~/sf1/forest.aiff")
    (24981 144332 0 0 60 0 1 0)
The loop info is a list of up to 4 points, the first two (start, end = 24981 144332 above) refer to the sustain loop, and the second two (0 0 above) refer to the release. The 5th and 6th list entries are the base note and detune values (60 0 above). For historical reasons, the 7th and 8th entries are the sustain and release modes (1 0 above). The looper instrument uses this function to implement a sort of "freeze" function. See also sound-loop-info.
mus-sound-mark-info (filename)
This function refers to the "mark" info that is sometimes found in aifc and aiff headers. It returns a list of lists (or an empty list if there are no marks), each inner list being (mark-id mark-position). The mark-id is a number that identifies it for use with mus-sound-loop-info, and the mark-position is its sample number in the file. Normally, this information is already included in the mus-sound-loop-info list:
    :(mus-sound-mark-info "/home/bil/sf1/forest.aiff")
    ((4 1) (3 0) (2 144332) (1 24981))
    :(mus-sound-loop-info "/home/bil/sf1/forest.aiff")
    (24981 144332 0 0 60 0 1 0)
mus-sound-maxamp (filename)
mus-sound-maxamp returns a list of max amps and locations thereof. The corresponding set! affects only the sndlib table of sound file info, not the sound file itself, as in all such cases.
    :(mus-sound-maxamp "oboe.snd")
    (24971 0.147247314453125)                  
    ;; oboe's maxamp is .147 first encountered at sample 24971
    :(mus-sound-maxamp "2a.snd")
    (933 0.0999755859375 2827 0.0999755859375) 
    ;; 2a's maxamps are 0.1 in each channel at sample 933 in chan 0, 2827 in chan 1
mus-sound-maxamp-exists? (filename)
This function returns #t if the sound's maxamp data is available in the sound cache; if it isn't, a call on mus-sound-maxamp has to open and read the data to get the maxamp.
    :(mus-sound-maxamp-exists? "/home/bil/test/sound/away.snd")
    #f
    :(mus-sound-maxamp "/home/bil/test/sound/away.snd")
    (14562264 0.463623046875 14557044 0.404571533203125)
    :(mus-sound-maxamp-exists? "/home/bil/test/sound/away.snd")
    #t
mus-sound-open-input (filename)
mus-sound-open-input opens the sound file 'filename' and returns an integer for use with mus-sound-read and mus-sound-close-input.
mus-sound-open-output (filename :optional (srate 44100) (chans 1) data-format header-type comment)
mus-sound-open-output creates a new sound file with the indicated attributes, and returns an integer for use with mus-sound-write and mus-sound-close-output.
mus-sound-prune ()
mus-sound-prune removes all defunct (non-existent) files from the sound cache. This is primarily intended for internal testing (snd-test.scm).
mus-sound-read (fd beg end chans sd)
mus-sound-read reads data from the sound file 'fd', loading the sound-data object 'sd' from 'beg' to 'end'.
mus-sound-reopen-output (filename :optional (chans 1) data-format header-type data-location)
mus-sound-reopen-output reopens the sound file 'filename', ready to continue writing output.
mus-sound-report-cache (file)
This function prints the current sound header data table to the file given or stdout if none is specified.
mus-sound-samples (filename)
mus-sound-samples returns the number of samples in the sound file 'filename' according to its header.
    :(mus-sound-samples "oboe.snd")
    50828
mus-sound-seek-frame (fd frame)
mus-sound-seek-frame moves the input or output reading point to 'frame' in the sound file 'fd'.
mus-sound-srate (filename)
mus-sound-srate returns the sampling rate of 'filename'.
mus-sound-type-specifier (filename)
This is the original type indication of 'filename'. This is only useful in internal testing.
mus-sound-write (fd beg end chans sd)
mus-sound-write writes data from the sound-data object 'sd' to the sound file 'fd'.
mus-sound-write-date (filename)
This returns the sound's write date:
    :(strftime "%d-%b %H:%M %Z" (localtime (mus-sound-write-date "oboe.snd")))
    "18-Oct 06:56 PDT"
mus-sun-set-outputs (speakers headphones line-out)
This sets the current Sun audio outputs. Each entry should be either 0 (turn off the device) or 1 (turn it on). On NetBSD, use mus-netbsd-set-outputs.

See Sndlib for more information on these functions. When called from Snd, these throw 'mus-error upon encountering an error, rather than returning -1 like the underlying sndlib functions.

The following function uses the sndlib functions to mimic the 'info' popup menu option (see examp.scm for a version that uses format):

(define info 
  (lambda (file)
    (string-append 
     file
     ": chans: " (number->string (mus-sound-chans file))
     ", srate: " (number->string (mus-sound-srate file))
     ", " (mus-header-type-name (mus-sound-header-type file))
     ", " (mus-data-format-name (mus-sound-data-format file))
     ", len: " (number->string 
                (/ (mus-sound-samples file) 
                   (* (mus-sound-chans file) (mus-sound-srate file)))))))



Marks

A mark is an object that refers to a particular sample. Each mark has an associated sample number (mark-sample), name (mark-name), sync value (mark-sync), and a globally unique id number (returned by find-mark or add-mark). See Marks in snd.html for an overview and key bindings associated with marks. See also the hooks section above for various mark-related hooks.

add-mark (sample :optional snd chn name sync)

add-mark adds a mark at the position 'sample', returning the new mark' identifier (an integer). Since marks can move around during editing, a unique tag is needed to refer to a particular mark; we call this thing its 'id' throughout this section. The identifier is used to specify the mark in all the mark-related functions such as mark-sample:
    :(define m1 (add-mark 1234))
    #<unspecified>
    :(mark-sample m1)
    1234
The mark-name can be set via 'name', and the mark-sync field via 'sync'. If 'sample' is beyond the end of the data, add-mark throws 'no-such-sample. There is also the form add-mark! which returns #f if the sample number is beyond the current last sample, rather than throwing the 'no-such-sample error.
delete-mark (id)
delete-mark deletes the mark identified with 'id'. Mark additions and deletions follow the edit list, so if the deleted mark was present in an earlier edit, and you undo to that point, the mark comes back to life.
delete-marks (:optional snd chn)
This function deletes all marks in the given channel. It could be defined in Scheme as:
    (for-each 
      (lambda (m) 
        (delete-mark m)) 
      (marks (or snd (selected-sound)) (or chn (selected-channel))))
find-mark (samp :optional snd chn edpos)
find-mark returns the id of the mark at sample 'samp' or #f if none is found. If 'samp' is a string, rather than an integer, find-mark looks for a mark of that name. mark-name->id in marks.scm finds a named mark in any channel (a global version of find-mark).
mark-color
This sets the color of mark indicator; the default is red. mark-sync-color uses mark-color to display all sync'd marks with some distinctive color.
mark-context
This is the graphics context to use to draw a mark (XOR mode).
mark-home (id)
mark-home is a list with the sound index and channel that hold the mark identified by 'id'. This provides a way to go from a mark id to its sound and channel.
    :(marks 0 0)   ; what marks are in snd 0, chn 0?
    (0)            ; just one with id 0
    :(mark-home 0) ; what is mark 0's snd/chn?
    (0 0)
mark-name (id)
This is the name of the mark identified by 'id'.
    :(define m1 (add-mark 1234 0 0 "a name"))
    #<unspecified>
    :(mark-name m1)
    "a name"
    :(set! (mark-name m1) "a new name")
    "a new name"
    :(mark-name m1)
    "a new name"
mark-sample (id :optional edpos)
mark-sample is the sample number (a location) marked by the mark 'id' at edit history position 'edpos'.
    :(mark-sample m1)
    1234
    :(set! (mark-sample m1) 4321)
    4321
    :(mark-sample m1)
    4321
It might be more consistent with other Snd names to call this mark-position, but I wanted to emphasize that a mark follows its sample around as a sound is edited; that is, it marks a sample, not a position in the sound. Say we have three named marks in a speech excerpt (see below), then delete the initial spoken word ("now"); each mark backs up with the deletion so that it continues to point to its original sample:
marks
mark-sync (id)
This is the mark's sync field (the default is 0). The sync value provides a way to group marks for simultaneous changes. Marks that share the same sync value (if not 0), move together when any one of them is dragged, play together if clicked, etc. To find which marks share a given sync value, use syncd-marks; to find an unused sync value use mark-sync-max.

Marks that are syncd together can be used for insertions, and deletions, and can set arbitrary groups of play points. But it's a bit tedious to type (set! (mark-sync ...)...) for each of the marks you want in the group. The following code example uses the mark-click-hook instead; you type (start-sync), then click each of the marks that you want grouped together, then (stop-sync).
(define mark-sync-number 0)
(define (start-sync) (set! mark-sync-number (+ (mark-sync-max) 1)))
(define (stop-sync) (set! mark-sync-number 0))
(define (click-to-sync id) (set! (mark-sync id) mark-sync-number) #f)
(add-hook! mark-click-hook click-to-sync)
Now control-click and drag one of them, and all of them move together deleting data, or inserting zeros; or click the "play" triangle, and all of them play together starting from their respective samples.
mark-sync-max ()
This is the maximum mark sync value seen so far.
mark-tag-height ()
When a mark is drawn, it has a horizontal rectangle at the top, then a vertical line, then a triangle. The line marks the marked sample, the triangle can be clicked to play from the mark, and the rectangle can be clicked or dragged. The mark-tag-height refers to the vertical thickness of that tag in pixels; its default is 4.
mark-tag-width ()
This is the mark tag width in pixels; it defaults to 10.
marks (:optional snd chn edpos)
This function returns a list of mark ids in the given channel at the edit history position 'edpos'. If 'chn' and 'edpos' are omitted, a list of lists is returned, each inner list representing a channel of 'snd'. If 'snd' is also omitted, a list of lists of lists is returned, representing each sound and its channels.
(define (how-many-marks-in-channel snd chn)
  (length (marks snd chn)))

(define (how-many-marks-in-sound snd)
  (apply + (map length (marks snd))))

(define (how-many-marks)
  (apply + (map how-many-marks-in-sound (sounds))))
marks without any argument, or with just a sound index returns a list of lists; each inner list is the list of current marks (ids) active in that channel, ordered by sample number. If the channel argument is specified, marks returns just the list of mark ids. If the edit history position is given, the list of ids reflects the mark list at that time in the edit history. See describe-mark in marks.scm.

Say we have two sounds open, 2 marks in the first (a mono sound), and one in the 2nd channel of the 2nd (a stereo sound):
    :(marks 0 0)
    (0 1)     ; these are mark id's, as returned by the add-mark function for example
    :(marks 1)
    (() (2))  ; no mark in channel 0, one in channel 1
    :(marks)
    (((0 1)) (() (2)))

    Ruby:
    :marks()
    [[[0, 1]], [nil, [2]]]

    Forth:
    snd> marks
    '( '( '( 0 1 ) ) '( nil '( 2 ) ) )
mark? (id)
mark? returns #t if mark 'id' is active (that is, if it is present in a currently open channel).
save-marks (:optional snd filename)
save-marks saves the given sound's marks, writing a Scheme, Ruby, or Forth source file named either 'filename' or <sound's file-name>.marks. It returns the file name or #f if there are no marks to save.
show-marks
show-marks is #t if marks are being displayed.
syncd-marks (sync)
syncd-marks is a list of marks (the mark id's) that share the mark-sync value 'sync'.
(define (move-syncd-marks sync diff)
  (for-each 
    (lambda (m) 
      (set! (mark-sample m) (+ (mark-sample m) diff))) 
    (syncd-marks sync)))

See marks.scm for more examples including:


global find-mark: mark-name->id
mark history: describe-mark
synchronize marks by inserting silences: syncup
squeeze selection between marks: fit-selection-between-marks
insert silence before marks: pad-marks
move syncd marks: move-syncd-marks
play starting from syncd marks: play-syncd-marks
evaluate function between marks: eval-between-marks
place marks at selection start and end: snap-marks
define selection via marks: define-selection-via-marks
force dragged mark to land on a beat: snap-mark-to-beat
loop continuously between the two specified marks: loop-between-marks
split sound into separate files based on mark placement: mark-explode
mark property lists: mark-property
save mark properties in saved state file: save-mark-properties
show mark properties upon click: mark-click-info

Other examples can be found in Dave Phillips' marks-menu.scm, snd-motif.scm (add-mark-pane), edit-menu.scm (trim from mark, etc), examp.scm (move window to correspond to mark, looping).



Mixes

Mixing operations have a lot of extra support built into Snd. In nearly every mixing function, you can request a "mix tag" (or set that request globally via with-mix-tags). If the mix operation is tagged, you can then operate on that data through a number of functions, the Mix Dialog, various hooks, and various mouse-related actions.

Mixes

A mix is an object that represents a channel (one channel in and one channel out) of a sound mix. Each mix object has a unique identifier called its 'id' that identifies it in the following functions. Say we have a mix whose id is 123:

    >(set! (mix-amp 123) .5)
    .5

This sets mix 123's amplitude scaler to .5.

mix (file :optional samp in-chan snd chn with-mix-tags auto-delete)

mix is one of the basic mixing functions. It mixes the 'in-chan' channel of the file 'file' into the given channel starting at 'samp' in the output channel. If 'in-chan' is #t, all input channels are mixed into successive output channels. mix returns the mix id (of the first mix, if there are multiple channels).

If 'with-mix-tags' is #f (the default is #t), the data is simply mixed without creating any mix objects.

  (mix "test.snd")             ; add channel 0 of test.snd to the current sound at sample 0
  (mix "test.snd" 0 #t)        ; same but add all channels of test.snd into successive output channels
  (mix "test.snd" 0 1)         ; add channel 1 of test.snd to channel 0 of the current sound
  (mix "test.snd" 0 0 #f 1)    ; add channel 0 of test.snd to channel 1 of the current sound
  (mix "test.snd" 0 3 #f 1 #f) ; add channel 3 of test.snd to channel 1 of the current sound, without a mix tag

The input file ('file') is not deleted by Snd unless 'auto-delete' is #t (or 1 or 3). auto-delete can be a boolean (#f = don't delete), or and integer: 0=don't delete, 1=delete, 3=delete but keep track of multichannel inputs.

In the next example, we mix in two sounds:

mixes
Now we can drag either of the red tags to move the mixed sounds, call up the View:Mixes dialog to edit them, or use the functions in this section. For example, we'll set the amplitude of the first and the position of the second:
mixes
We can use dlocsig in conjunction with mix to move the mixed-in sound:
(if (not (provided? 'snd-dlocsig.scm)) (load-from-path "dlocsig.scm"))
(if (not (provided? 'snd-ws.scm)) (load-from-path "ws.scm"))

(define (mix-move-sound start-time file path)
  "mix file at start-time in the currently selected sound following the given dlocsig path"
  (let* ((duration (mus-sound-duration file))
	 (rd (make-sample-reader 0 file))
	 (start (seconds->samples start-time))
         (tmp-sound (with-temp-sound (:channels (channels) :srate (mus-sound-srate file))

         ;; We use with-temp-sound here rather than sound-let because mix normally expects its input file to
         ;; be around until it decides it doesn't need it anymore, but sound-let deletes all its temp files.
         ;; We use with-temp-sound rather than with-sound because the latter would want to open the output
         ;; file in Snd; this could be turned off by including the :to-snd #f argument.

                       (let* ((vals (make-dlocsig :start-time 0
				                  :duration duration
				                  :path path))
		              (dloc (car vals))
		              (beg (cadr vals))
		              (end (caddr vals)))
	                (run
		         (lambda ()
		           (do ((i beg (+ 1 i)))
		               ((= i end))
		             (dlocsig dloc i (read-sample rd)))))))))

         ;; now tmp-sound is the name of a temp sound file that moves 'file' in a spiral

    (mix tmp-sound start #t #f #f (with-mix-tags) #t)))

;;; (mix-move-sound 0 "oboe.snd" (make-spiral-path :turns 3))
mixes (:optional snd chn edpos)
mixes returns a list of the mixes (their id numbers) in the given channel at the edit history position 'edpos'. If the channel argument is omitted, you get a list of lists, each inner list referring to a single channel of that sound. If the sound index is also omitted, you get a list of lists of lists, the outer list referring to each sound, each inner list to that sound's channels. Say we have two sounds open, 2 mixes in the first (a mono sound), and 1 mix in the 2nd channel of the 2nd (a stereo sound):
    :(mixes 0 0)
    (0 1)     ; these are mix id's, as returned by the mix function for example
    :(mixes 1)
    (() (2))  ; no mix in channel 0, one in channel 1
    :(mixes)
    (((0 1)) (() (2)))

    Ruby:
    :mixes()
    [[[0, 1]], [nil, [2]]]

    Forth:
    snd> mixes
    '( '( '( 0 1 ) ) '( nil '( 2 ) ) )
mix-amp (mix)
mix-amp is the amplitude scaler applied to the mix. To make mix 0 half as loud:
  (set! (mix-amp 0) .5)
mix-amp-env (mix)
mix-amp-env is the amplitude envelope applied to the mix (a list of breakpoints). To reset this to its default (null) state, use #f.
    (set! (mix-amp-env 0) '(0 0 1 1))
sets mix 0's envelope to a ramp.
mix-color (:optional mix)
This is the color of mix waveforms; it defaults to dark-gray. If you want to set just a particular mix's color, give the id of the mix as the 'mix' argument: (set! (mix-color) red) sets all mix waveforms to red; but (set! (mix-color 3) red) sets only mix 3's waveform to red.
mix-length (mix)
mix-length returns the mix's length in samples.
mix-home (mix)
mix-home returns a list containing the mix's output sound index and channel number, and the input original filename (if any), and input channel.
    :(mix "pistol.snd" 1000)
    1
    :(mix-home 1)
    (0 0 "/home/bil/cl/pistol.snd" 0)
    ;; (list output-sound-index output-channel input-filename input-channel)
    :(mix-vct (make-vct 100 .1) 2000)
    2
    :(mix-home 2)
    (0 0 #f 0)
    ;;   #f: no input file
mix-name (mix)
mix-name is the mix's name, if any. The mix name is displayed near the mix tag. See also mix-name->id. Here's an example that uses the mix name and the tag location (mix-tag-y) to provide some pitch feedback:
(if (not (provided? 'snd-v.scm)) (load-from-path "v.scm"))
(if (not (provided? 'snd-ws.scm)) (load-from-path "ws.scm"))

(define (frequency->tag-y freq lo octs) ; tag height dependent on freq
  (inexact->exact (round (* 100 (- 1.0 (/ (log (/ freq lo)) (* (log 2.0) octs)))))))

(let ((violin-sync 1)
      (violin-color (make-color 0 0 1)) ; blue
      (cello-sync 2)
      (cello-color (make-color 0 1 0)) ; green
      (index (new-sound "test.snd" :channels 1 :size (* 44100 22))))
  
  (define (violin beg dur freq amp)
    (let ((id (mix (with-temp-sound ()                            ; write instrument output to temp sound
   	             (fm-violin 0 dur (->frequency freq #t) amp)) ; our favorite FM instrument
		(->sample beg) 0 index 0  ; mix start, file in-chan, sound, channel
                #t #t)))                  ; mix with tag and auto-delete
      (if (symbol? freq)
	  (set! (mix-name id) (symbol->string freq)))
      (set! (mix-sync id) violin-sync)
      (set! (mix-color id) violin-color)
      (set! (mix-tag-y id) (frequency->tag-y (->frequency freq #t) (->frequency 'c2) 3))))
  
  (define (cello beg dur freq amp)
    (let ((id (mix (with-temp-sound ()
   	             (fm-violin 0 dur (->frequency freq #t) amp :fm-index 1.5))
                (->sample beg) 0 index 0
  	        #t #t)))
      (if (symbol? freq)
	  (set! (mix-name id) (symbol->string freq)))
      (set! (mix-sync id) cello-sync)
      (set! (mix-color id) cello-color)
      (set! (mix-tag-y id) (frequency->tag-y (->frequency freq #t) (->frequency 'c2) 3))))
  
  (as-one-edit
   (lambda ()
     (violin 0 1 'e4 .2)  (violin 1 1.5 'g4 .2)  (violin 2.5 .5 'g3 .2)
     (cello  0 1 'c3 .2)  (cello  1 1.5 'e3 .2)  (cello  2.5 .5 'g2 .2)
     
     (violin 3 3 'f4 .2)
     (cello  3 3 'd3 .2)
     
     (violin 6 1 'e4 .2)   (violin 7 1 'g3 .2)   (violin 8 1 'e4 .2)
     (cello  6 1 'c3 .2)   (cello  7 1 'g2 .2)   (cello  8 1 'c3 .2)
     
     (violin 9 3 'd4 .2)
     (cello  9 3 'b2 .2))))
mix name example

But note names are a bother to read; musglyphs.scm has code to display notes using CMN glyphs. Here we use the draw-mix-hook to display our notes as a score:

mix CMN example

In more complex cases, using a mix per note fills the screen with mix tags; it's probably cleaner to use multiple output files, collecting related notes in one file, then mixing these at the end:

;; open two output files, one for the violin notes, the other for the cellos
;;   then mix them into "test.snd"

(let ((violins (make-sample->file "violins.snd" 1 mus-lfloat mus-next))
      (cellos (make-sample->file "cellos.snd" 1 mus-lfloat mus-next)))

  (define (violin beg dur freq amp)
    (with-temp-sound (:continue-old-file #t :output "violins.snd") 
      (fm-violin beg dur (->frequency freq #t) amp)))

  (define (cello beg dur freq amp)
    (with-temp-sound (:continue-old-file #t :output "cellos.snd") 
      (fm-violin beg dur (->frequency freq #t) amp :fm-index 1.5)))

  (violin 0 1 'e4 .2)  (violin 1 1.5 'g4 .2)   (violin 2.5 .5 'g3 .2)
  (cello  0 1 'c3 .2)  (cello  1 1.5 'e3 .2)  (cello  2.5 .5 'g2 .2)
  ;; etc

  (let ((index (new-sound "test.snd" :channels 1))) ; our overall output file
    (mix "violins.snd")
    (mix "cellos.snd"))

    (mus-close violins)
    (mus-close cellos))

See also with-mixed-sound in ws.scm.

mix-position (mix)
mix-position is the current starting position (a sample number) of 'mix'. To move mix 0 so that it starts at sample 200 in the output:
    (set! (mix-position 0) 200)
mix-properties (mix)
mix-properties is a property list associated with a mix. mix-property (in mix.scm) reads and writes this list.
mix-region (:optional samp reg snd chn reg-chan)
mix-region mixes region 'reg's' channel 'reg-chan' into the given channel starting at sample 'samp' ('samp' defaults to the cursor sample). It returns the new mix id, if any.
mix-selection (:optional beg snd chn selection-chan)
mix-selection mixes the current selection's channel 'selection-cha' into the given channel starting at 'beg', returning the new mix's id. The Edit:Mix selection menu choice is essentially (mix-selection (cursor)).
mix-speed (mix)
mix-speed is the speed (resampling ratio) of 'mix'; 1.0 (the default) means no resampling takes place; 2.0 causes the mix data to be read twice as fast.
mix-sync (mix)
mix-sync is an integer, like sync that you can use to group mixes. See mix.scm for many examples.
mix-sync-max ()
This is the maximum mix sync value seen so far.
mix-tag-height ()
This is the mix tag height (the vertical extent of the tag rectangle) in pixels (the default is 14).
mix-tag-width ()
This is the mix tag width in pixels (the default is 6).
mix-tag-y (mix)
This is the mix tag y (vertical) offset; 0 (the default) is the top of the graph, so higher tag-y values position the tag lower in the graph. For example, if you know the frequency of the mix sound, you can reflect that in the tag height with:
   (set! (mix-tag-y mix-id) 
         (inexact->exact (round (* 100 (- 1.0 (/ (log (/ freq 40.0)) (* (log 2.0) 7)))))))
mix-vct (vct :optional beg snd chn with-mix-tags origin)
mix-vct is one of the basic mixing functions. It mixes the contents of 'vct' into the given channel starting at sample 'beg'. If 'with-mix-tags' is #f (the default is #t), the data is simply mixed without creating any mix tags. mix-vct returns the id of the new mix, or -1 (a simple mix, no tag).
    :(channel->vct 1000 3)               ; what are these 3 samples before the mix?
    #<vct[len=3]: -0.065 -0.059 -0.060>
    :(mix-vct (vct .1 .2 .3) 1000)       ; add these 3 values
    3
    :(channel->vct 1000 3)               ; now we hope they were added...
    #<vct[len=3]: 0.035 0.141 0.240>
    :(mix-position 3)                    ; and the new mix starts at 1000
    1000
    :(mix-length 3)                      ; and its length is 3 samples
    3
    :(mix-vct (with-sound (:output (make-vct 44100)) (fm-violin 0 1 440 .1)))
    0
mix-waveform-height ()
This is the maximum height in pixels of mix waveforms. The default is 20 (see show-mix-waveforms).
mix? (id)
mix? returns #t if the mix 'id' exists (that is, it is accessible in a channel's edit list). It returns the id if the given mix is currently active. Otherwise mix? returns #f.
play-mix (mix :optional beg)
play-mix plays the mix 'mix'. 'beg' is where to start playing within the mix. This function does not return until the play is complete or is interrupted (it is similar to play-and-wait, rather than play).
with-mix-tags ()
If with-mix-tags is #f (the default is #t), newly mixed data does not have a mix id or tag associated with it.


mix sound file: mix or drag-and-drop it where you want it mixed
mix channel: see mix-channel in extensions.scm
mix region: mix-region
mix selection: mix-selection
mix vct: mix-vct
mix sound-data: mix-sound-data
mix a frame: mix-frame
enveloped mix: see enveloped-mix in extensions.scm
read mix samples: make-mix-sample-reader
mix data maxamp: mix-maxamp
mix data to vct: mix->vct
save mix data in file: save-mix
mix property list: mix-property in mix.scm
pan mono sound into stereo: see place-sound in examp.scm
move a mixed sound via dlocsig: mix-move-sound
the mix dialog: Mix Dialog
cross-fade in frequency: cross-fade and dissolve-fade in fade.scm
zipper cross-fade: zipper.scm
snap mix to beat after drag: snap-mix-to-beat
delete all mixes: silence-all-mixes
with-sound (a notelist) expanded into mixes: with-mixed-sound



Regions and Selections


Regions

A region is a saved portion of sound data. Use the View:Region browser to inspect, edit, and save regions. As regions are defined, the new ones are pushed on a stack, and if enough regions already exist, old ones are pushed off (and deleted) to make room. Each region has a unique id returned by make-region and shown beside the region name in the Region Browser. Most of the region arguments default to the current region (the top of the regions stack).

forget-region (:optional reg)

forget-region deletes region 'reg', removing it from the region stack. This does not affect any of the active sounds; it just tells Snd that you no longer need any access to one of the current regions. To delete all regions,
    (for-each forget-region (regions))
I called this forget-region because delete-region seemed ambiguous, especially given delete-selection.
insert-region (:optional beg reg snd chn)
insert-region inserts region 'reg' at sample 'beg' in the given channel. The following function uses insert-region (and other region functions) to rotate the samples in a channel:
(define* (rotate-channel :optional (samps 1) snd chn)
  (let* ((ind (or snd (selected-sound) (car (sounds))))
	 (chan (or chn (selected-channel) 0)))
    (let ((reg (make-region 0 (- samps 1) ind chan)))
      (as-one-edit
       (lambda ()
	 (delete-samples 0 samps ind chan)
	 (insert-region (frames ind chan) reg)))
      (forget-region reg))))
make-region (:optional beg end snd chn)
make-region creates a new region spanning the samples 'beg' to 'end' in the given channel. It returns the new region's id. If no arguments are given, the current selection is used. If 'chn' is #t, all chans are included, taking the sync fields into account.
make-region-sample-reader (:optional start reg chn (dir 1))
This creates a sample-reader reading the region's channel 'chn' starting at sample 'start' within that region. 'dir' can be 1 (read forwards) or -1 (read backwards).
mix-region (:optional samp reg snd chn)
mix-region mixes region 'reg' into the given channel starting at sample 'samp' (defaulting to the cursor location). It returns the id of the first channel's mix (subsequent channels simply increment this number).
play-region (:optional reg wait stop-func)
play-region plays region 'reg'; if wait is #t, it plays to the end before returning. The 'stop-func' can be a function which is called when the play process stops. Its one argument is the reason the play is stopping; it will be 0 if the play completed normally. To see all possible reasons, see play. Here is a brief example (taken from play.scm) that plays a region over and over until someone types C-g:
  (play-region 0 #f
    (letrec ((stop-func (lambda (reason)
			  (if (and (not (c-g?))
				   (= reason 0)) ; 0 -> play completed normally
			      (play-region reg #f stop-func)))))
      stop-func))
region-chans (:optional reg)
This returns the number of channels in the region 'reg'.
region-frames (:optional reg chan)
region-frames returns the number of frames in the region 'reg'.
    :(make-region 100 200)
    1
    :(region-frames 1)
    101
region-graph-style (style)
region-graph-style is the graph drawing choice for the region dialog's graph. The choices are:
    graph-lines  graph-dots  graph-filled  graph-lollipops  graph-dots-and-lines 
graph-lines is the default.
region-home (:optional reg)
This returns a list with the name of the source file for the given region, its start time in the original data, and its length in frames.
region-maxamp (:optional reg)
region-maxamp is the peak amplitude of the samples in the region 'reg'.
    :(region-maxamp 1)
    4.8828125e-4
region-maxamp-position (:optional reg)
region-maxamp-position returns the location (a sample number) of the peak amplitude of the region 'reg'.
region-position (:optional reg chan)
region-position returns the begin time of the region's channel 'chan' in the original sound.
    :(make-region 1000 2000)
    2
    :(region-position 2)
    1000
region-sample (:optional samp reg chn)
region-sample returns the value of the sample 'samp' in channel 'chan' of the region 'reg'.
region->vct (:optional samp samps reg chn v)
region->vct returns a vct containing 'samps' samples starting at 'samp' in channel 'chn' of the region 'reg'. If 'v' (a vct) is provided, it is filled, rather than creating a new vct.
(define (region-rms n)
  (let* ((data (region->vct 0 0 n)) ; len=0 => entire region
	 (len (vct-length data)))
    (sqrt (/ (dot-product data data len) len))))
region-srate (:optional reg)
region-srate returns the sampling rate of the data that makes up the region 'reg'.
regions ()
regions returns a list of active region ids. The most recently created region is (car (regions)). (map region-frames (regions)) returns a list of region lengths. The maxmimum length of this list is set by max-regions.
region? (:optional reg)
region? returns #t if the region 'reg' exists. There is a limit to how many regions Snd tries to keep track of (max-regions); when necessary, the least-recently created region is deleted.
save-region (reg &optional-key :file :header-type :data-format :comment)
save-region saves the region 'reg' in 'file' in the given data format and header type. It returns the output filename. The arguments after 'reg' are optional-key args (that is, they are normal keyword arguments, but the keywords are optional). The following calls are equivalent:
    (save-region 2 "reg0.snd")
    (save-region 2 :file "reg0.snd" :header-type mus-next)
    (save-region 2 "reg0.snd" mus-next mus-bfloat "a comment")
    (save-region 2 :file "reg0.snd" :comment "a comment" :data-format mus-bfloat)

Max length of region list: max-regions
Whether selection creates a region: selection-creates-region
To play region repeatedly: play-region-forever
Start region browser from Scheme: view-regions-dialog
All about regions: regions
The region dialog: region browser
Region rms amp: region-rms
region-play-list and region-play-sequence in examp.scm
region->frame
region->sound-data
make-region-frame-reader



Selections


convolve-selection-with (file :optional amp)

convolve-selection-with convolves the current selection with 'file', replacing the selection with the result. 'amp' sets the maxamp of the result.
delete-selection ()
delete-selection deletes the selection, equivalent to the Edit:Delete selection menu choice.
env-selection (envelope :optional env-base)
env-selection applies 'envelope' to the selection. (as an amplitude envelope). 'envelope' can also be a CLM env generator; in this case, 'env-base' is ignored. These are equivalent:
  (env-selection '(0 0 1 1 2 0))
  (env-selection (make-env '(0 0 1 1 2 0) :length (selection-frames)))
filter-selection (env :optional order truncate)
filter-selection applies an FIR filter of order 'order' and frequency response 'env' to the selection. 'env' can be the filter coefficients themselves in a vct with at least 'order' elements, or a CLM filtering generator (see filter-sound). If 'truncate' is #t (the default), the filter output is truncated at the selection end. If 'truncate' is #f, the extra output ('order' samples worth) is mixed into the stuff following the selection.
insert-selection (:optional beg snd chn)
insert-selection inserts a copy of the selection starting at 'beg' in the given channel (that is, it pastes in the selection as a block). The Edit:Insert selection menu choice is essentially (insert-selection (cursor)).
mix-selection (:optional beg snd chn)
mix-selection mixes (adds) a copy of the selection starting at 'beg' in the given channel, and returns new mix id. The Edit:Mix selection menu choice is (mix-selection (cursor)).
play-selection (:optional wait edpos stop-func)
play-selection plays the selection. 'edpos' is the edit position which default to the current edit. If 'wait' is #t, play-selection does not return until the play has completed. If 'stop-func' is a procedure of one argument, it is called when the play process stops. The argument provides the reason the play is stopping; it will be 0 if the play completed normally. See play-region for an example, and play for a complete list of these reasons.
reverse-selection ()
reverse-selection reverses the selection.
save-selection (&optional-key :file (:header-type mus-next) :data-format :srate :comment :channel)
save-selection saves the selection in 'file'. If 'channel' is given, it saves only that channel. See popup.scm for an example.
(define (brksnd dur base)
  "(brksnd dur base) divides the current sound into dur-sized pieces, 
saving them in files named 'base'.n: (brksnd 1.0 \"sec\")"
  (let ((hop (inexact->exact (* (srate) dur)))
	(len (frames))
        (old-sync (sync)))
    (set! (sync) 1) ; save all chans
    (do ((i 0 (+ i hop))
	 (j 0 (+ 1 j)))
	((>= i len))
      (make-selection i (+ i hop)) ; in extensions.scm
      (save-selection (string-append base "." (number->string j))))
    (set! (sync) old-sync)))
(define* (extract-channels #:rest chans)
  ;; extract a list of channels from the current sound and save as test.snd: (extract-channels 0 2)
  (let ((snd (or (selected-sound) (car (sounds)))))
    (if (sound? snd)
	(begin
	  (for-each
	   (lambda (chan)
	     (set! (selection-member? snd chan) #t)
	     (set! (selection-position snd chan) 0)
	     (set! (selection-frames snd chan) (frames snd chan)))
	   chans)
	  (save-selection "test.snd")))))
scale-selection-by (scalers)
scale-selection-by scales (multiplies) the selection by 'scalers' which can be either a float, a list of floats, or a vct. In a multichannel selection, each member of the vct or list is applied to the next channel in the selection. (scale-selection-by '(0.0 2.0)) scales the first channel by 0.0, the second (if any) by 2.0. (scale-selection-by 2.0) scales all channels by 2.0. Normally the order of channels follows the order of the sound indices.
scale-selection-to (:optional norms)
scale-selection-to normalizes the selection to peak amplitude 'norms' which can be either a float, a list of floats, or a vct.
select-all (:optional snd chn)
This function selects all samples in the given channel. If a region is created, it returns the new region's id.
selection-chans ()
selection-chans returns the number of channels in the current selection.
selection-frames (:optional snd chn)
selection-frames returns the number of frames in the current selection (its length in samples). You can set this to move the selection end point:
    :(select-all)                    ; grab all of current channel
    1
    :(selection-frames)
    55240
    :(set! (selection-frames) 10000) ; unselect all but the starting 10000
    10000
    :(selection-frames)
    10000
    :(set! (selection-frames) (* 2 (selection-frames))) ; double the selection length
    20000
See also make-selection.
selection-maxamp (:optional snd chn)
selection-maxamp returns the peak amplitude of the selection in the given channel. I use this to provide a view of the selection amplitude envelope in the envelope editor. If you select 'selection' and 'wave' in that dialog, it displays a copy of whatever is in the main channel graph, so to get a display that makes it easy to "connect the dots", I use C-x m:
    (bind-key #\m 0
      (lambda ()
        (set! (y-bounds (selected-sound) (selected-channel)) (list 0 (selection-maxamp))))
        #t)

    (bind-key #\m 4
      (lambda ()
        (set! (y-bounds (selected-sound) (selected-channel)) (list -1.0 1.0)))
        #t)
The second key binding (C-x C-m), undoes the previous C-x m. Another useful key binding in this regard is C-x v, the built-in command to fill the current window with the selection.
selection-maxamp-position (:optional snd chn)
selection-maxamp-position returns the location (a sample number) of the peak amplitude of the selection in the given channel.
selection-member? (:optional snd chn)
selection-member? returns #t if the given channel has data that is currently selected. This is mostly useful when adding a channel to the current selection; see make-selection in extensions.scm. If 'snd' is #t and the new value is #f, the entire selection is deactivated
    (set! (selection-member? #t) #f)
ie equivalent to unselect-all.
selection-position (:optional snd chn)
selection-position is the sample where selection begins. You can set this to move the selection's starting point to some arbitrary sample. If changed, the selection end point stays the same, while the length (selection-frames) changes to reflect the moved origin. See make-selection in extensions.scm.
selection-srate ()
This function returns the selection srate. There's some arbitrariness in this if the sounds that make up the selection have different sampling rates.
selection? ()
selection? returns #t if there is a selection.
    :(select-all)
    2
    :(selection?)
    #t
    :(set! (selection-member? #t) #f)
    #f
    :(selection?)
    #f
smooth-selection ()
smooth-selection applies a smoothing function to the selection, producing a sinusoid between the selection end points. In normal use, you'd bind this function to some key, select a portion (say a few samples) of a sound around a click, then smooth it by typing that key.
src-selection (num-or-env :optional base)
src-selection applies sampling rate conversion to the selection; this is the same as src-sound but as applied to the selection.

show the current selection: show-selection
color of selected portion: selection-color
set whether creating a selection creates a region: selection-creates-region
fft graph refers to the selection: show-selection-transform
hook called when selection stops playing: stop-playing-selection-hook
swap chans in selected portion: swap-selection-channels
replace portion with selection: replace-with-selection
select portion via function: make-selection
evaluate func on each sample of selection: eval-over-selection (map-selection in effect)
selection members as list of lists of sound indices and channels: selection-members
rms of selection data: selection-rms
delete selection and smooth the splice: delete-selection-and-smooth
select portion between two marks: define-selection-via-marks
place marks at selection start and end: snap-marks
squeeze selection between marks: fit-selection-between-marks
add context-sensitive popup menu specific to selection: add-selection-popup
delete selection and write it to a file: cut-selection->new
append selection: append-selection
write selection to a file: selection->new
notch filter selection: notch-selection
undo select-all.: deselect-all
filter the selection: filter-selection, filter-selection-and-smooth

The selected portion can be chosen, independent of any region, by setting selection-position and selection-frames. It's easy to extend the notion of a selection to an arbitrary list of sound portions:

(define (make-section . members)
  ;; each member is '(beg dur snd chn)
  (append (list 'Section) members))

(define (section-for-each func section)
  ;; call func on each member of the section
  (as-one-edit (lambda () (for-each func (cdr section)))))

;; an example that scales each member of the section by .5
(section-for-each 
 (lambda (sect)
   (apply scale-channel (append (list .5) sect)))
 (make-section (list 0 10000 0 0) (list 30000 10000 0 0)))



Sounds and channels

This is the heart of Snd; we've waded through all the ancillary junk, and we've finally reached the functions that actually edit sounds! Most of these functions take both a sound index and a channel number. When the function refers to a variable that can be set locally on a sound (zero-pad, for example), the 'snd' and 'chn' arguments can be #t, referring to all current sounds or all channels of a sound; this possibility is identified below by displaying the arguments in this weird brownish color: snd or snd chn. (I tried green, but it is illegible, and I'm already using red and blue all over the place). In cases where it makes sense, if the 'snd' argument is omitted, the reference is to the global default value. So, (set! (amp-control-bounds) '(0.0 2.0)) sets the global amp control (slider) bounds to be between 0.0 and 2.0, whereas (set! (amp-control-bounds snd) '(0.0 2.0)) sets it only for the sound referred to by 'snd'.

Many of the procedures also have an 'edpos' argument (standing for "edit position"). It always defaults to the current edit history position. If specified, it can be either an edit history position (to which the operation is applied), the constant current-edit-position (the default), or a function of two arguments, the sound index and the channel number. The function should return the desired edit history position. In most cases, you should only refer to edits in the past (that is, 'edpos' should be less than or equal to the current edit-position); in a few situations, you can make use of data in the "redo" section of the edit-history list, but nothing is guaranteed.

For not-very-good historical reasons (it took me awhile to decide how to organize things), some of the procedures here are unnecessarily inconsistent in what arguments they accept, whether a channel of #t signals application to all channels or just the selected one, whether the sync field is followed, and so on. Rather than make a bunch of backwards incompatible changes, I decided to add a bunch of more-or-less synonymous functions that regularize these calls. The replacements always take arguments in the order begin time, duration (not end sample), sound index, channel number, and edit position, possibly preceded by one argument, and sometimes followed by an edit history name or 'ring time' (overlap). The sync field is ignored, an unspecified sound argument applies only to the current sound, and an unspecified channel argument applies only to the current channel. The following substitutions can be made:

convolve-with file amp s c e                    clm-channel convolve-gen beg dur s c e
env-sound env beg dur base s c e                env-channel env beg dur s c e
filter-sound env order s c e                    filter-channel env order beg dur s c e trunc
insert-silence beg dur s c                      pad-channel beg dur s c e
insert-sound file beg filechn s c e             insert-channel filedat beg dur s c e
mix file beg filechn s c with-tags              mix-channel filedat beg dur s c e
play beg s c sync end e                         play-channel beg dur s c e
redo edits s c                                  redo-channel edits s c
reverse-sound s c e                             reverse-channel beg dur s c e
scale-by scls s c                               scale-channel scl beg dur s c e
scale-to scls s c                               normalize-channel norm beg dur s c e
set-samples beg dur data s c trunc origin fchan vct->channel vct beg dur s c e
smooth-sound beg dur s c                        smooth-channel beg dur s c e
src-sound num base s c e                        src-channel ratio-or-env beg dur s c e
undo edits s c                                  undo-channel edits s c
apply-ladspa reader dat dur origin snd chn      ladspa-channel dat beg dur s c e

Another case that might deserve "regularization" is make-sample-reader which confusingly interpolates the direction argument between the channel and edit-position:

   (define* (read-channel :optional (beg 0) snd chn edpos (direction 1))
     (make-sample-reader beg snd chn direction edpos))

The edit position argument can cause ambiguity in a few cases. What should Snd do with: (pad-channel 100 0 snd chn 2)? It currently treats any 0-length operation as a no-op, so the edit history is not changed by this function call. However, in a similar situation (where the current edit counter is greater than 2, so this code is reaching back into the edit history list): (scale-channel 1.0 0 #f snd chn 2) Snd essentially copies the state of the channel at that edit position, and puts it in the current edit position. There's never any good reason to do this, so if it looks like a no-op, do it a different way.


add-player (player :optional start end edpos stop-proc out-chan)
add-player adds 'player' to the play-list (see make-player). If 'edpos' is given, play at that edit position. 'stop-proc' can be a procedure of one argument; it is called when the play process stops and passed the reason the play is stopping; it will be 0 if the play completed normally (the other possibilities are listed here, but they really aren't interesting). The 'out-chan' argument is the audio output channel to send the data to; it defaults to the channel number of the player's channel in the containing sound (that is, the default is to send channel 1 data to channel 1 of the DAC, and so on).
(define* (play-mono-as-stereo :optional snd)
  "(play-mono-as-stereo snd) sends the channel 0 data in 'snd' to all available DAC channels."
  (let ((vals (make-vct 3))
	(end (frames snd)))
    (mus-audio-mixer-read mus-audio-default mus-audio-channel 3 vals)
    (let ((chans (max (inexact->exact (vct-ref vals 0)) 2))) ; assume stereo is out there
      (do ((chan 0 (+ 1 chan)))                               ; get a player for each output channel
	  ((= chan chans))
	(let ((player (make-player snd 0)))
	  (add-player player 0 end current-edit-position #f chan)))
      (start-playing chans (srate snd)))))
See play-with-envs in enved.scm, play-syncd-marks in marks.scm, or start-dac in play.scm.
axis-info (snd chn :optional grf)

axis-info returns a list describing the specified axis:
    (list left-sample right-sample 
          x0 y0 x1 y1 x-min y-min x-max y-max 
          x0-position y0-position x1-position y1-position y-offset 
          xlabel ylabel new-peaks)
This can be useful if you're drawing arbitrary figures in a graph. 'grf' defaults to time-graph; the other choices are transform-graph and lisp-graph. 'x0' is the time in seconds corresponding to the left-sample (the left edge of the graph). Similarly 'y0' is the lower y axis limit as a sample value (i.e. -1.0). 'x-max' is the sound's duration in seconds ('x-min' is always 0.0). The "positions" are pixel values, in drawing area coordinates; these give the position of the graph in the drawing area. 'y-offset' refers to "united" graphs where several channels share one drawing area. You can use it to translate mouse coordinates to channel number in that situation. For example, x->position could be:
(define (x->position-1 x snd chn)
  (let* ((axinfo (axis-info snd chn time-graph))
	 (x0 (list-ref axinfo 2))
	 (x1 (list-ref axinfo 4))
	 (axis-left (list-ref axinfo 10))
	 (axis-right (list-ref axinfo 12)))
    (inexact->exact 
     (+ axis-left
	(* (- x x0) 
	   (/ (- axis-right axis-left)
	      (- x1 x0)))))))
Here's a key binding that uses axis-info to save every channel's graph position upon "Page Down", then restore that state upon "Page Up":
(bind-key "Page_Down" 0 
	  (lambda ()
	    (let ((last-page-state 
	            (map (lambda (snd) 
			   (let ((data (list snd (file-name snd))))
			     (do ((i 0 (+ 1 i)))
 				 ((= i (chans snd)) data)
			       (set! data (append data (list (cons i (axis-info snd i))))))))
		         (sounds))))
	      (bind-key "Page_Up" 0
			(lambda ()
			  (if last-page-state
			      (for-each
			       (lambda (lst)
				 (let ((snd (list-ref lst 0))
				       (name (list-ref lst 1)))
				   (if (and (sound? snd) 
					    (string=? (file-name snd) name))
				       (for-each
					(lambda (chan-data)
					  (let ((chn (list-ref chan-data 0))
						(x0 (list-ref chan-data 3))
						(x1 (list-ref chan-data 5))
						(y0 (list-ref chan-data 4))
						(y1 (list-ref chan-data 6)))
					    (set! (x-bounds snd chn) (list x0 x1))
					    (set! (y-bounds snd chn) (list y0 y1))))
					(cddr lst)))))
			       last-page-state)))))))
See also draw-smpte-label in snd-motif.scm, or make-current-window-display in draw.scm.
beats-per-measure (:optional snd chn)
The x axis labelling of the time domain waveform can be in measures (x-axis-style = x-axis-in-measures); this variable sets the number of beats per measure. The default is 4.
beats-per-minute (:optional snd chn)
The x axis labelling of the time domain waveform can be in beats (x-axis-style = x-axis-in-beats) or in measures (x-axis-in-measures); this variable sets the number of beats per minute. The default is 60.0, making it the same as x-axis-in-seconds. See snap-mark-to-beat, or snap-mix-to-beat.
bomb (:optional snd on)
bomb displays an exploding bomb icon next to 'snd's' name (in the minibuffer area). Set 'on' to #f to erase the bomb. Each time bomb is called, the bomb icon moves to the next image in its sequence (showing the bomb's fuse burning down), restarting the sequence whenever it reaches the end. This icon is used when a sound and its underlying file get out of sync somehow (auto-update).
(define show-bomb 
  (lambda (n speed) 
    (if (> n 0) 
	(begin 
	  (bomb) 
	  (in speed (lambda () (show-bomb (- n 1) speed))))
	(bomb 0 #f))))

(show-bomb 15 200) ; there are 15 images in the sequence
channel-amp-envs (file chan size :optional peak-file-func work-proc-func)
channel-amp-envs returns two vcts of length 'size' containing the peak-amp envelopes of the channel 'chan' of file 'file'. 'peak-file-func' if any is used to get the name of the associated peak-env-info file if the file is very large. 'work-proc-func' is called when the amp envs are ready if the amp envs are gathered in the background. If 'file' is a sound index (an integer), 'size' is an edit-position, and the current amp envs (if any) are returned. The arguments to 'peak-file-func' are the file and the channel. If it returns a string, that is treated as the filename to read to get the peak info. The arguments to 'work-proc-func' are the filename, the channel and the current peak. make-sound-icon in make-sound-box in snd-motif.scm uses this function to draw the little thumbnail graph for each sound icon.
channel-data (:optional snd chn)
channel-data provides very low-level access to the data currently in the given channel's sample buffers. It is used by the variable-display mechanism to show graphs of variable values (normally in an instrument). channel-data only works with sound indices returned by make-variable-display, and only in a float-sample version of Snd (i.e. not one that was built with the configure argument --without-float-samples). See make-variable-display in snd-motif.scm.
channel-properties (:optional snd chn)
channel-properties is a property list associated with a channel. It is set to '() at the time a sound is opened, so it provides a relatively simple way to save data about a channel which will automatically be erased when the channel is closed. channel-property (in extensions.scm) reads and writes this list.

Traditionally in Lisp, a property list has been treated as an association list. This is a list of pairs (made by cons), each inner pair having a key as its first element, and the associated value as the second element. The function assoc can be used to search the list for a given key's value; a new key-value pair can be added with:
    (cons (cons key value) a-list)
In Common Lisp, property lists have other properties, so to speak, but channel-properties (and sound-properties) can be handled in any way you like. See channel-sync in extensions.scm for a brief example; more elaborate examples are in enved.scm (enved-envelope), or draw.scm (colored-samples and insert-envelope).
channel-style (:optional snd)
channel-style reflects the value of the 'unite' button in multichannel files. Possible values are channels-separate, channels-combined (the default), and channels-superimposed. The following code sets the 'unite' button if the current sound has more than 4 channels:
(add-hook! after-open-hook 
  (lambda (snd)
    (if (> (chans snd) 4)
        (set! (channel-style snd) channels-combined))))
channel->vct (:optional beg dur snd chn edpos)
channel->vct returns a vct with the specified data. In Ruby, the "->" in a function name is translated to "2", so the function call is:
    v = channel2vct(0, 100)
(define* (selection->vct :optional snd chn)
  (if (selection-member? snd chn)
      (channel->vct (selection-position snd chn)
		    (selection-frames snd chn)
		    snd chn)
      (if (selection?)
          (throw 'no-such-channel 
                 (list "selection->vct"
     	               (format #f "snd ~D channel ~D is not a member of the selection" snd chn)))
	  (throw 'no-active-selection (list "selection->vct")))))
See also mark-explode in marks.scm.
channels (:optional snd)
chans (:optional snd)
This function returns the number of channels in 'snd'. It can be set, but the result is a new version of the underlying sound with the header changed to reflect the new number of channels. That is, no new data is created, but the existing data is reapportioned to the new channels: (set! (channels) 2); this is not undo-able (except by calling it again with the original number of channels — the data is not touched).
clear-minibuffer (:optional snd)
This clears the sound's minibuffer area (both the text and the error message widgets).
clm-channel (clm-gen :optional beg dur snd chn edpos overlap origin)
clm-channel applies 'clm-gen' to the given channel starting at sample 'beg' for 'dur' samples, and 'overlap' samples of 'ring time'. This is used by some of the regularized functions, but it can also be used directly:
(define* (convolve-channel kernel :optional nbeg ndur nsnd nchn nedpos)
  (let* ((beg (or nbeg 0)) 
	 (snd (or nsnd (selected-sound) (car (sounds))))
	 (chn (or nchn (selected-channel)))
	 (dur (or ndur (- (frames snd chn) beg)))
	 (edpos (or nedpos current-edit-position))
	 (reader (make-sample-reader beg snd chn 1 edpos))
	 (cgen (make-convolve :filter kernel 
                              :input (lambda (dir)
				       (read-sample reader)))))
    (clm-channel cgen beg dur snd chn edpos)
    (free-sample-reader reader)))

(define (difference) (clm-channel (make-two-zero 1 -1)))
(define (wobble) (clm-channel (make-ncos 50 3)))
(define (hold-nose) (clm-channel (make-ncos 1 3)))
(define (bad-reception) (clm-channel (make-ncos 10 5)))
close-sound (:optional snd)
This closes 'snd' (the same as the File:Close menu item). To close all sounds:
    (close-sound #t) 
    ;; equivalent to:
    (for-each close-sound (sounds))
Before the sound is actually closed, before-close-hook is called, then close-hook, then the sound is closed.
comment (:optional snd)
This returns the sound's comment, if any; when a sound is opened, the comment is taken from the file's header (the same as mus-sound-comment). If you set it, the header is not updated until the sound is saved. If the new comment is the only change you want to make, you can save the new header via the Edit:Edit Header menu option.
convolve-with (file :optional amp snd chn edpos)
This convolves the given channel (or the currently sync'd data) with the data in the sound file 'file'. 'amp' is the resultant peak amplitude (leave 'amp' unset, or set it to #f to get the unnormalized result). Convolve-with in conjunction with mix can provide high-quality reverb:
(define conrev
  (lambda (impulse amp)
    (convolve-with impulse amp)
    (save-sound-as "reverb.snd") ;let mix scalers set reverb amount
    (revert-sound)
    (mix "reverb.snd")))
count-matches (proc :optional sample snd chn edpos)
This returns how many samples satisfy the function 'proc'; 'proc' should take one argument (the current sample value), and return #t for a hit. 'sample' determines where to start the search.
    Scheme: (count-matches (lambda (y) (> y .1)))

    Ruby:   count_matches(lambda do |y| y > 0.1 end)

    Forth:  lambda: <{ y }> y 0.1 f- f0< ; count-matches
count-matches is modelled after Emacs. It could be defined along these lines:
(define (count-matches proc)
  (let ((count 0))
    (scan-channel 
      (lambda (y)
        (if (proc y) (set! count (+ count 1)))
        #f))
    count))
cursor (:optional snd chn edpos)
This returns the cursor location (as a sample number; the first sample is numbered 0) in channel 'chn' of 'snd'. (set! (cursor) 100) moves the cursor to sample 100. The cursor is somewhat similar to a mark in that it moves if you delete or insert samples in front of it.

Tracking cursor: with-tracking-cursor (cursor-follows-play was the old name)
Change cursor shape or size: cursor-style, cursor-size
Cursor moving keys: Moving the Cursor
Display data about sample under cursor: verbose cursor

cursor-follows-play (:optional snd)
This is #t if the cursor is following along in the sound as it plays. The new name of this variable is with-tracking-cursor.
cursor-position (:optional snd chn)
This gives the current cursor position as a list (x y). These graph-relative values can be turned into axis-relative values with position->x and position->y:
    (position->x (car (cursor-position)))
    ;; equals:
    (/ (cursor) (srate))
cursor-size (:optional snd chn)
This gives the cursor size in pixels; it defaults to 15. (set! (cursor-size) 30) makes the cursor twice as big as usual.
cursor-style (:optional snd chn)
The cursor style is cursor-cross, cursor-line, or a cursor-drawing function. The default cursor shape is a "+" sign; the cursor-line is a vertical line. As a function, cursor-style is a procedure of three arguments, the sound index, channel number, and a boolean that is true if the cursor is currently tracking playback (a "tracking-cursor"). The procedure should draw the cursor at the current cursor position using the cursor-context. One example is smart-line-cursor in draw.scm. Here is a simpler one that replaces the normal "+" cursor with an "x":
(define (x-cursor snd chn ax)
  (let* ((point (cursor-position))
         (x (car point))
         (y (cadr point))
         (size (inexact->exact (/ (cursor-size) 2))))
    (draw-line (- x size) (- y size) (+ x size) (+ y size) snd chn cursor-context)    
    (draw-line (- x size) (+ y size) (+ x size) (- y size) snd chn cursor-context)))

(set! (cursor-style) x-cursor)
data-format (:optional snd)
This returns the sound's data format — the encoding used for the sound samples (e.g. mus-bshort).

The standard formats nowadays are mus-bshort (big-endian 16-bit integers), mus-bfloat (32-bit big-endian floats), and mus-bint (32-bit big-endian integers), and the corresponding little-endian versions: mus-lshort, mus-lfloat, and mus-lint. If you're using an Intel-style PC, you're using a little-endian machine; Old macs (PowerPC Macs) and Suns use big-endian (NeXT, SGI, and Atari also used it in the good old days). If you write a Next file and use little-endian data, some programs other than Snd may complain; similarly, RIFF wants little-endian and AIFC wants big-endian data (both can handle the other kind, but most sound-related programs don't know that). In the old days, when disk space was at a premium, 8-bit formats were used a lot: mus-mulaw and mus-alaw (kludges for a kind of 8-bit float), mus-byte and mus-ubyte (8-bit ints, unsigned in the latter case). A few DACs want a particular kind of data, but Snd handles that conversion internally. Anything less than 12 bits will sound bad — Perry Cook's book "Real Sound Synthesis" has examples.

If you encounter a file with an unknown format, or a header that has the wrong format, you can set this field to force Snd to interpret the data in any way you like. Similar remarks apply to the srate, data-location, header-type, and channels fields. There are ambiguities in some header specifications, usually involving big/little endian or signed/unsigned data confusion. If you encounter a sound that is clipping crazily or is just a burst of noise, try changing these settings. Some NeXT/Sun (au) header files using byte-wide data assume the byte is unsigned, whereas most others assume it is signed. Sndlib treats it as signed by default, so to make one of the unsigned-byte files playable,
    (set! (data-format) mus-ubyte)
Float data is another source of confusion; there is apparently no agreement on whether the data is between -1.0 and 1.0, or -32768.0 and 32767.0 or anything else. In this case, Snd assumes -1.0 to 1.0 (except in one special case involving IRCAM headers), and you may have to set y-bounds to see the actual data. Yet another gotcha: files with 32-bit integers. Some programs (Glame, apparently, and perhaps Ardour) assume the fraction is 31 bits wide, others (Snd) use whatever its sample width is configured to be; there is no correct or standard placement of the fixed point, but not to worry! Your data is ok: (set! (y-bounds) (list -256.0 256.0)). There are several ways you can handle these files automatically in Snd. Perhaps the simplest is to use one of the open hooks:
(add-hook! after-open-hook 
  (lambda (snd) 
    ;; this could also (alternatively) set the y-bounds as above
    (if (= (data-format snd) mus-lint)
        (set! (data-format snd) mus-lintn))))
or (an alternative that sets the underlying database entry, rather than the current in-Snd choice):
(add-hook! open-hook 
  (lambda (name)
    (if (= (mus-sound-data-format name) mus-lint)
        (set! (mus-sound-data-format name) mus-lintn))
    #f))
If you set any of these fields, the sound's index may change (there can be an embedded update-sound). To deal with MPEG, OGG, Flac, or Speex files, see examp.scm (mpg) or misc.scm (mpg123 and ogg123). Octave/WaveLab ASCII files can be translated by read-ascii (examp.scm).

To turn a data-format number into a string, use mus-data-format-name. To get the data format of some sound file, use mus-sound-data-format. The default output (new-sound, and save-sound-as) data-format is default-output-data-format. To change a sound file's data-format, use save-sound-as.
data-location (:optional snd)
This gives the location (in bytes) of the sound samples in the file represented by 'snd'. In a raw (headerless) file, this is 0, but normally the data comes after some portion of the header. To get the data-location of some sound file, use mus-sound-data-location. If you set this field (you don't want to do this — it is a law of nature that you will forget the original setting!), the underlying file is immediately rewritten.
data-size (:optional snd)
This gives the size (in bytes) of the sound data in the file represented by 'snd'. If you set this field, the underlying file is immediately rewritten (the header is changed; I don't think the file is truncated, but no matter what happens, it is not my fault). Next/Sun files treat the size field as purely "advisory", so an incorrect data size is often ignored in that case.
delete-sample (samp :optional snd chn edpos)
This deletes sample 'samp' in the given channel.

delete a file: use the Scheme function delete-file, Ruby's File.delete, or Forth's file-delete
delete a region: forget-region
delete the currently selected samples: delete-selection
delete the selection and smooth the splice: delete-selection-and-smooth
delete a mark or all marks: delete-mark
delete a colormap: delete-colormap
delete samples: delete-samples
remove a file from the sound cache: mus-sound-forget
remove a menu item: remove-from-menu or remove-main-menu in snd-motif.scm
delete a mix or all mixes: silence-mixes
add a 'delete' option to the file selection dialog: add-delete-option
Scheme delete funcs: remove-if assoc-remove! hash-remove! delete-if! delete! string-delete

delete-samples (samp samps :optional snd chn edpos)
This deletes a block of samples. The deleted portion starts at sample 'samp' and runs for 'samps' samples. See delete-to-zero or delete-selection-and-smooth in extensions.scm.
dot-size (:optional snd chn)
This gives the size in pixels of dots when graphing with dots (default: 1); this affects graph-styles such as graph-lollipops. See graph-hook or auto-dot in examp.scm.
env-channel (clm-env-gen :optional beg dur snd chn edpos)
env-channel is the regularized version of env-sound. 'clm-env-gen' can be either a CLM envelope generator or an envelope (a list of breakpoints). (env-channel '(0 0 1 1 2 0)). To get .1 seconds of attack and decay:
  (let ((dur (/ (frames) (srate)))) 
    (env-channel (list 0 0  .1 1  (- dur .1) 1  dur 0)))
An envelope in Snd is a list of breakpoints. It can be packaged as a CLM generator (an 'env') via make-env. It can be declared via define just like any other variable, or with defvar (for CLM/Snd intercompatibility), or with define-envelope.


envelopes in Snd:
envelope sound: env-channel, env-sound
Other enveloping functions: ramp-channel, xramp-channel, smooth-channel
The CLM env generator: env, many examples in examp.scm, new-effects.scm, etc
Various operations on envelopes: env.scm
Peak env files: peak-env.scm
The envelope editor: Edit or View and Envelope
Panning: place-sound in examp.scm
Envelope over mix: enveloped-mix
Local envelope editor: enved.scm, xm-enved.scm
Read sound indexed through envelope: env-sound-interp
Cosine as envelope: cosine-channel, cosine-channel-via-ptree, bell-curve
envelope with sinusoidal connections between points: sine-env-channel
envelope with separate base for each segment: powenv-channel
envelope with x^2 connections: env-squared-channel
envelope with x^n connections: env-expt-channel
envelope with ncos connections: blackman4-env-channel
Customizing the envelope editor: enved-hook
peak amp follower: moving-max

env-channel-with-base (envelope-or-env-gen :optional base beg dur snd chn edpos)
env-channel-with-base is a slight variation on env-channel. There are times when it's a bother to call make-env just to get an exponential envelope.
env-sound (envelope :optional samp samps env-base snd chn edpos)
env-sound applies the amplitude 'envelope' to the given channel starting at sample 'samp' for 'samps' samples with connecting segments based on 'env-base'. 'env-base' defaults to 1.0. 'samp' defaults to 0. 'samps' defaults to the full duration. 'envelope' is a list containing the breakpoint values (as in CLM) or an env generator.
ampenvs
(env-sound '(0 0 1 1 2 0))

env_sound([0.0, 0.0, 1.0, 1.0, 2.0, 0.0])

'( 0.0 0.0 1.0 1.0 2.0 0.0 ) env-sound
ampenvs
As mentioned in sndclm.html, 'env-base' determines how the break-points are connected. If it is 1.0 (the default), you get straight line segments. 'env-base' = 0.0 gives a step function (the envelope changes its value suddenly to the new one without any interpolation). Any other positive value becomes the exponent of the exponential curve connecting the points. 'env-base' < 1.0 gives convex curves (i.e. bowed out), and 'env-base' > 1.0 gives concave curves (i.e. sagging). If you'd rather think in terms of e^-kt, set 'env-base' to (exp k). See env.lisp for a CLM instrument that shows the relation between the connecting curve's exponent and 'env-base'. Here's a brief restatement:
(define (compare-exp k)
   (let ((e (make-env (list 0 1 1 (exp (- k))) :base (exp k) :length 11)))
      (do ((i 0 (+ 1 i )))
         ((= i 10))
         (snd-print (format #f "~A ~A~%" (env e) (exp (* (- k) (/ i 10.0))))))))
If 'envelope' is a CLM env generator, 'env-base' is ignored.
fft-log-frequency (:optional snd chn)
This returns whether the spectrum frequency axis is logarithmic (#t) or linear (#f, the default). If logarithmic, the lower end is set by log-freq-start which defaults to 32Hz.
fft-log-magnitude (:optional snd chn)
This returns whether the spectrum magnitude axis is in decibels (#t) or linear (#f, the default). If in decibels, the minimum displayed is set by min-dB which defaults to -60.
fft-window (:optional snd chn)
This sets the choice of fft data window (default: blackman2-window)
  bartlett-hann-window     bartlett-window        blackman2-window       blackman3-window
  blackman4-window         bohman-window          cauchy-window          connes-window       
  dolph-chebyshev-window   exponential-window     flat-top-window        gaussian-window     
  hamming-window           hann-poisson-window    hann-window            kaiser-window
  parzen-window            poisson-window         rectangular-window     riemann-window      
  samaraki-window          tukey-window           ultraspherical-window  welch-window        
  blackman5-window         blackman6-window       blackman7-window       blackman8-window       
  blackman9-window         blackman10-window      rv2-window             rv3-window
  rv4-window               mlt-sine-window        papoulis-window        dpss-window
  sinc-window

The Hann window is sometimes called Hanning in the DSP literature, apparently as an in-joke. For an extensive discussion of these windows, see Fredric J. Harris, "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform", Proceedings of the IEEE, Vol. 66, No. 1, January 1978, with updates from: Albert H. Nuttall, "Some Windows with Very Good Sidelobe Behaviour", IEEE Transactions of Acoustics, Speech, and Signal Processing, Vol. ASSP-29, 1, February 1981, and of course, Julius Smith's DSP web site.

fft-window-alpha (:optional snd chn)
The ultraspherical window has two "family" parameters; the one named "mu" is called "beta" here, to parallel its use in related windows; the other one, named "xmu" is named "alpha" here, for no good reason. fft-window-alpha sets the shape of the side lobes; see "Design of Ultraspherical Window Functions with Prescribed Spectral Characteristics", Bergen and Antoniou, EURASIP JASP 2004 (also available on-line) for details.
fft-window-beta (:optional snd chn)
Some fft windows have a parameter, often named alpha or beta, that chooses one from a family of possible windows. The actual (underlying) beta values are dependent on the window choice, but in Snd, fft-window-beta is scaled to fit the current window's range of values, so its value here should fall between 0.0 and 1.0.
fft-with-phases (:optional snd chn)
This returns whether the single FFT display includes phase information (the default is #f).
file-name (:optional snd)
the sound's complete (or "absolute") file name; the directory is included; see short-file-name if you don't want all the directory junk. See examp.scm for many examples.
filter-channel (env :optional order beg dur snd chn edpos trunc origin)
The regularized version of filter-sound. If the end of the filtered portion is not the end of the sound, the 'trunc' argument determines whether the filtered sound is truncated at that point (the default: #t), or mixed with the overlapping section, similar to the truncate argument to filter-selection. 'env' can be either the frequency response envelope, or a vct containing the desired coefficients.
filter-sound (env :optional order snd chn edpos origin)
filter-sound applies an FIR filter of order 'order' (actually one more than the nominal order) and frequency response 'env' to the given channel. 'env' can also be a vct containing the filter coefficients, or any CLM filtering generator (e.g. comb, formant, one-pole, iir-filter, etc). The generator is called in C, not Scheme, so this is the fastest way to apply CLM filtering to a sound. See also clm-channel.
(filter-sound '(0 1 1 0) 1024)                             ; FIR filter given frequency response
(filter-sound (vct .1 .2 .3 .3 .2 .1) 6)                   ; FIR filter given actual coefficients
(filter-sound (make-fir-filter 6 (vct .1 .2 .3 .3 .2 .1))) ; CLM FIR filter
(filter-sound (make-delay 120))                            ; CLM delay (same as insert-silence)
(filter-sound (make-formant 1200 .99))                     ; CLM formant
(filter-sound (make-filter 2 (vct 1 -1) (vct 0 -0.99)))    ; remove DC
If you want to use the cascade filter structure, rather than the canonical form of CLM's filter generator:
    (define (make-biquad a0 a1 a2 b1 b2)
      (make-filter 3 (vct a0 a1 a2) (vct 0.0 b1 b2)))
If you have coefficients for the cascade form, but have no scruples about using some other form, see cascade->canonical in dsp.scm, and the examples that follow.


Filters in Snd:
filter a sound: filter-sound, filter-channel, and clm-channel
filter the selection: filter-selection, filter-selection-and-smooth
CLM filter generators: filter, one-pole, formant, comb, notch, all-pass, etc
lowpass filter: make-lowpass in dsp.scm
highpass filter: make-highpass in dsp.scm
bandpass filter: make-bandpass in dsp.scm
bandstop filter: make-bandstop in dsp.scm
the usual analog filters (Butterworth, Chebyshev, Bessel, Elliptic): analog-filter.scm
Butterworth filters: make-butter-high-pass, make-butter-low etc in dsp.scm, used in new-effects.scm
IIR filters of various orders/kinds: dsp.scm
Hilbert transform: make-hilbert-transform in dsp.scm
differentiator: make-differentiator in dsp.scm
block DC: see example above, dc-block in prc95.scm, clean-channel in clean.scm, or stereo-flute in clm-ins.scm
hum elimination: see eliminate-hum and notch-channel in dsp.scm
hiss elimination: notch-out-rumble-and-hiss
smoothing filters: moving-average, weighted-moving-average, exponentially-weighted-moving-average
notch-filters: notch-channel and notch-selection
arbitrary spectrum via FIR filter: spectrum->coeffs in dsp.scm
invert an FIR filter: invert-filter in dsp.scm
filtered echo sound effect: flecho in examp.scm
time varying filter: fltit in examp.scm
draw frequency response: use envelope editor or filter control in control panel
Moog filter: moog.scm
Savitzky-Golay filter: savitzky-golay-filter
Click reduction: remove-clicks, clean-channel
FIR filter as virtual edit: virtual-filter-channel
LADSPA-based filter effects: see ladspa.scm
Max Mathews resonator: firmant, maxf.scm, maxf.rb
Spectral edit dialog: Envelope Editor
graphical equalizer filter bank: graphEq
nonlinear (Volterra) filter: volterra-filter
Kalman filter: kalman-filter-channel
see also convolution, physical modeling, reverb, and fft-based filtering
Scheme srfi-1 filter function: %filter.

find-channel (proc :optional sample snd chn edpos)
This function finds the sample that satisfies the function 'proc'. 'sample' determines where to start the search. If 'proc' returns some non-#f value, find-channel returns a list with that value (if optimization is off) and the sample number. In the find dialog and in C-s or C-r searches, if the value returned is an integer, the cursor is offset by that number of samples.
>(find-channel (lambda (y) (> y .1)))
(#t 4423)

>(find-channel (lambda (y) (and (> y .1) 'a-big-sample)))
(a-big-sample 4423) ; if optimization is on, this will be (#t 4423)

>lambda: <{ y }> 0.1 y f< ; find-channel
'( #t 4423 )
I didn't bring out the search direction (find-channel is built on scan-channel which assumes it is scanning forwards), but it's not hard to write a function to find in reverse:
(define* (find-channel-in-reverse proc :optional beg snd chn edpos)
  (let* ((sample (or beg (- (frames snd chn) 1))) ; or cursor?
	 (reader (make-sample-reader sample snd chn -1 edpos))
	 (result #f))
    (do ((i sample (- i 1)))
	((or result (< i 0)) 
	 (and result
	     (list result (+ 1 i))))
      (set! result (proc (reader))))))

find a mark: find-mark
find a mix: find-mix
find a sound: find-sound
Example find procedures: search-for-click, zero+, next-peak, find-pitch
Search via continuation: scan-again
Explicit access to search procedures: search-procedure
The Find dialog: Find or find-dialog
find silence: map-silence, scramble-channel in examp.scm
find zero crossing: find-zero
find any difference between two chans: channels-equal
see also count-matches and scan-channel
search a multichannel sound: scan-sound
find a widget: find-child in snd-motif.scm
add C-s and C-r to the listener key bindings: add-find-to-listener in snd-motif.scm
Scheme find: find-if

find-sound (filename :optional nth)
find-sound returns the index of 'filename' or #f if no sound is found that matches 'filename'. If there is (or might be) more than one file open with the given filename, the 'nth' parameter (which defaults to 0) chooses which to return. Leaving aside the 'nth' parameter, find-sound could be defined as:
(define (find-sound name)
  (call-with-current-continuation
   (lambda (return)
     (for-each 
      (lambda (snd)
	(if (or (string=? (short-file-name snd) name)
		(string=? (file-name snd) name))
	    (return snd)))
      (sounds))
     #f)))
See popup.scm, and files-popup-buffer, open-next-file-in-directory, and the "Buffer Menu" code in examp.scm.
finish-progress-report (:optional snd chn)
This ends an on-going progress report (a visual indication of how far along some time-consuming process is). See progress-report.
frames (:optional snd chn edpos)
This returns current length in samples of the channel 'chn'. Used with set!, this either truncates the sound or pads it with zeros at the end.
free-player (player)
free-player frees all resources associated with 'player' and remove it from the play-list.
graph (data :optional xlabel x0 x1 y0 y1 snd chn force-display show-axes-choice)
This function displays a graph of 'data' in a separate display per channel. The x axis is labelled 'xlabel', the x axis units go from 'x0' to 'x1' (the default is 0.0 to 1.0), the y axis goes from 'y0' to 'y1' (the default fits the data), and the display is associated with channel 'chn' in 'snd'.
    (graph (vct 0 .1 .2 .3 .4 .3 .2 .1 0) "roof")
The current slider values can be read from x-position-slider, x-zoom-slider, etc. The 'data' argument can be a list of vcts; each is graphed at the same time, following the sequence of colors used when channels are superimposed. If 'data' is a list of numbers, it is assumed to be an envelope (a list of breakpoints). If 'force-display' is #f (the default is #t), the graph is not explicitly drawn; this is useful when you're calling graph from the lisp-graph-hook, where the redisplay is automatic. 'show-axes-choice' sets the show-axes choice for the lisp graph.
(define display-energy
  ;; y-zoom-slider controls the graph amp
  (lambda (snd chn)
    "display time domain data as energy"
    (let* ((ls (left-sample))
           (rs (right-sample))
	   (datal (make-graph-data snd chn))
	   (data (if (vct? datal) datal (cadr datal)))
           (len (vct-length data))
           (sr (srate snd))
	   (y-max (y-zoom-slider snd chn)))
      (vct-multiply! data data)
      (graph data "energy" (/ ls sr) (/ rs sr) 0.0 (* y-max y-max) snd chn #f))))

(add-hook! lisp-graph-hook display-energy)
picture of examp.scm in action
graph-style (:optional snd chn)
graph-style determines how sound data is displayed (default: graph-lines). The choices are:
    graph-lines  graph-dots  graph-filled  graph-lollipops  graph-dots-and-lines 
In the set! case, if no 'snd' is specified, all graph-styles are set to the new value. If 'snd' is given, the three graph styles for that sound's channels (or channel 'chn') are set. See time-graph-style, lisp-graph-style, and transform-graph-style to override the default for a specific graph.
graphs-horizontal (:optional snd chn)
This determines whether channel graphs (the time domain, spectrum, and lisp graphs) are arranged vertically or horizontally (the latter is the default).
grid-density (:optional snd chn)
This controls the spacing of axis ticks; the default is 1.0. If grid-density is less than 1.0, more ticks are squeezed in; if greater than 1.0, fewer ticks are displayed. This mainly affects the grid display (show-grid).
header-type (:optional snd)
This returns the header type (e.g. mus-aiff) of the file that underlies 'snd'. Snd can read about 60 header types, and write 7 or so. "aiff" and "aifc" come from Apple, "riff" is the Microsoft "wave" header, "rf64" is the European Broadcast Union's 64-bit RIFF replacement, "nist" comes from the NIST-Sphere package, "next" or "sun" is the Next/Sun (".au") header, "ircam" is IRCAM's extension of the Next header, "caf" is Apple's 64-bit AIFC replacement, and "raw" means the sound file has no header. If you change the header type to "raw", any existing header is removed. Each header type has its own peculiarities; if in doubt, use mus-next because it is simple, and can handle any data format that Snd can write (whereas each of the others is restricted in this regard). The writable header types are mus-next, mus-nist, mus-aiff (obsolete, rarely needed), mus-aifc, mus-riff, mus-rf64, mus-caff, mus-ircam, and mus-raw (no header). For technical descriptions of the headers, see headers.c; for actual sound files, see sf.tar.gz at ccrma-ftp.

To turn a header type number into a string, use mus-header-type-name. To get the header type of some sound file, use mus-sound-header-type. If you set the header-type, the sound file is rewritten with the new header. The default output (new-sound, and save-sound-as) header type is default-output-header-type.

To read or write your own headers (or some header that isn't built-in), I recommend using either open-hook or open-raw-sound-hook: in the latter case, when you open the file with the unsupported header, Snd will throw up its hands and say "maybe it's a raw (headerless) sound"; it will then look at open-raw-sound-hook before trying other fallbacks (such as the Raw File Dialog). See examp.scm or misc.scm (MPEG, OGG, etc).
insert-sample (samp value :optional snd chn edpos)
This inserts sample 'value' at sample 'samp' in the given channel
insert some portion of a channel: insert-channel
insert a silence: pad-channel, insert-silence, pad-sound
insert a region: insert-region
insert the selection: insert-selection
insert a vct of samples: insert-samples, insert-vct
insert a sound: insert-sound or insert-file-dialog
append a sound and silence: append-sound
insert a frame: insert-frame
insert sound-data: insert-sound-data
insert-samples (samp samps data :optional snd chn edpos auto-delete origin)
This inserts 'samps' samples of 'data' (normally a vct) starting at sample 'samp' in the given channel. 'data' can be a filename. The regularized version of this is:
    (define* (insert-channel data :optional beg dur snd chn edpos)
      (insert-samples beg dur data snd chn edpos))
To insert a block of samples of a given value: (insert-samples beg dur (make-vct dur val)) If 'data' is a file, it is not deleted by Snd unless 'auto-delete' is #t.
insert-silence (beg num :optional snd chn)
This inserts 'num' zeros at 'beg' in the given channel. pad-channel is the regularized version, with one small change: insert-silence forces 'beg' to be within the current sound, but pad-channel pads out to 'beg' if 'beg' is past the end of the sound. (And, as usual in these cases, insert-silence follows the sync field, whereas pad-channel ignores it).
insert-sound (file :optional beg in-chan snd chn edpos auto-delete)
This inserts channel 'in-chan' of 'file' at sample 'beg' in the given channel. 'beg' defaults to the cursor position; if 'in-chan' is not given, all channels are inserted. To append one sound to another, padding at the end with some silence:
    (define* (append-sound file :optional (silence 1.0))
      (insert-sound file (frames))
      (insert-silence (frames) (inexact->exact (round (* (srate) silence)))))
'file' is not deleted by Snd unless 'auto-delete' is #t.
left-sample (:optional snd chn)
This returns the position in samples of the left edge of the time domain waveform for the given channel. To get the data currently displayed in the time domain window:
(define (window-samples)
  (let ((wl (left-sample))
	(wr (right-sample)))
   (samples wl (+ 1 (- wr wl)))))
See also move-one-pixel.

time domain window:
Built-in keyboard commands: Moving the Window
Specialized keyboard commands: bind-key
Window size: x-zoom-slider, zoom-one-pixel,
Window position: x-position-slider, move-one-pixel
Window left edge: left-sample
Window right edge: right-sample

fft window:
window size: drag x axis, spectrum-end
window start: spectrum-start
relation to time domain: before-transform-hook
selection fft: show-selection-transform

general info:
Axis description: axis-info

lisp-graph? (:optional snd chn)
lisp-graph? returns #t if the lisp-generated graph is currently displayed ("lisp" here means any extension language). The lisp graph section is also active if there's a drawing function on the lisp-graph-hook.
lisp-graph-style (:optional snd chn)
This determines how lisp-generated data is displayed. The choices are:
    graph-lines  graph-dots  graph-filled  graph-lollipops  graph-dots-and-lines 
make-player (snd chn)
This function makes a new player associated with the given channel. A player is a sort of wrapper for a channel of a sound that supports all the control-panel functions. Once created, you can set these fields, then call add-player to add this channel to the list of channels either being played (if a play is in progress) or about to be played. Once some player is in the play-list, you can start the play with start-playing, and stop it prematurely with either stop-player or stop-playing. These functions make it possible to build custom control panels. Here's a simple example that plays a sound with individual amplitudes for the channels:
(define play-with-amps
  (lambda (sound . amps)
    (let ((chans (chans sound)))
      (do ((chan 0 (+ 1 chan)))
          ((= chan chans))
        (let ((player (make-player sound chan)))
          (set! (amp-control player) (list-ref amps chan))
          (add-player player)))
      (start-playing chans (srate sound)))))

(play-with-amps 0 1.0 0.5) ;plays channel 2 of stereo sound at half amplitude
See play-with-envs in enved.scm, play-syncd-marks in marks.scm, start-dac in play.scm, and add-amp-controls in snd-motif.scm.
make-variable-graph (container :optional name length srate)
make-variable-graph is a part of the variable-display mechanism in snd-motif.scm. It creates the sound/channel pair that displays a graph or spectrum of the arbitrary data accessed via channel-data. See oscope.scm.
map-chan (func :optional start end edname snd chn edpos)
map-chan applies 'func' to samples in the specified channel. It is the old ("irregular") version of map-channel.
map-channel (func :optional beg dur snd chn edpos origin)
map-channel is one of the standard ways to change a sound. It applies 'func' to each sample replacing the current value with whatever 'func' returns. As usual, 'beg' defaults to 0, 'dur' defaults to the full length of the sound, 'snd' and 'chn' default to the currently selected sound, and 'edpos' to the current edit history list position. 'origin' is the edit history name of the current operation.

'func', a procedure of one argument (the current sample), can return #f, which means that the data passed in is deleted (replaced by nothing), or a number which replaces the current sample, or #t which halts the mapping operation, leaving trailing samples unaffected, or a vct the contents of which are spliced into the edited version, effectively replacing the current sample with any number of samples. This sounds more complicated than it is! Basically, a map-channel function receives each sample and returns either #f (no corresponding output), a number (the new output), or a bunch of numbers. If every value returned for a given channel is #f, the data is not edited. Here we add 0.2 to every sample in a channel:
Scheme:
    >(map-channel (lambda (y) (+ y 0.2)))
    0

Ruby:
    >map_channel(lambda do |y| y + 0.2 end)
    -0.0015869140625

Forth:
    >lambda: <{ y }> y 0.2 f+ ; map-channel
    -0.00158691

In the next sequence, we replace a sound by the difference between successive samples (a high-pass effect), then undo that by adding them back together, then check to see how close our reconstruction is to the original:

> (let ((y0 0.0)) (map-channel (lambda (y) (let ((diff (- y y0))) (set! y0 y) diff))))
0
> (let ((y0 0.0)) (map-channel (lambda (y) (let ((add (+ y y0))) (set! y0 add) add))))
0
> (let ((rd (make-sample-reader 0 0 0 1 0))) (map-channel (lambda (y) (- y (rd)))))
0          ; the sample reader is reading the unedited form of the sound
> (maxamp) ; i.e. how big is the biggest difference
0.0
(define* (cosine-channel :optional (beg 0) dur snd chn edpos)
  (map-channel
   (let* ((samps (or dur (frames snd chn)))
	  (incr (/ pi samps))
	  (angle (* -0.5 pi)))
     (lambda (y)
       (let ((val (* y (cos angle))))
	 (set! angle (+ angle incr))
	 val)))
   beg dur snd chn edpos))
Here's a slightly more involved example; we define a function that finds silent portions and replaces them with something:
(define (map-silence in-silence replacement)
  (let ((buffer (make-moving-average 128))
        (silence (/ in-silence 128)))
    (lambda (y)
      (let ((sum-of-squares (moving-average buffer (* y y))))
        (if (> sum-of-squares silence) y replacement)))))

(map-channel (map-silence .01 0.0))  ; squelch background noise
(map-channel (map-silence .001 #f))  ; remove silences altogether
Here we're using 'buffer', a CLM moving-average generator, to track the RMS value of the last 128 input samples. When that falls below the argument 'silence', we replace the current sample with 'replacement'. It may be easier in complex cases to use with-sound rather than map-channel. See step-src for example.

It is possible to break out of a map, flushing any edits, via call-with-current-continuation:
(define ctr 0)
(call-with-current-continuation 
  (lambda (return)
    (map-channel (lambda (val)
                   (set! ctr (+ 1 ctr)) 
                   (if (> ctr 100) 
                     (return "quitting!") 
                     val)))))
It is also possible to stop, then continue map-channel:
(define go-on #f)
(map-channel (lambda (y) 
               (call-with-current-continuation 
                 (lambda (stop) 
                   (if (> y 1.0)
                       (begin 
                         (set! go-on stop) 
                         (throw 'oops))))) 
               .2))
If this hits a sample > 1.0, it will print 'oops and put the continuation in the variable 'go-on'. (go-on) will continue where you left off. (I'm not sure how far this can be pushed, or whether it's a good idea — you may end up with unclosed files and so on).

If the editing action is not mapping something over the current sound, it is safest to write a temp file with the new data, then pass that to set-samples with the 'trunc' argument set to #t. This way you don't assume the new sound will fit in memory (as in using vct->channel for example). Use snd-tempnam to get a temporary filename that reflects the current temp-dir setting. The env-sound-interp function in examp.scm is an example of this.
(define* (map-sound-chans proc :optional (beg 0) dur snd edpos origin)
  (do ((i 0 (+ 1 i)))
      ((= i (chans snd)))
    (map-channel proc beg dur snd i edpos origin)))
An esoteric aside: map-channel sets up the sample reader before calling the procedure, so if that procedure edits the sound itself (independent of map-channel), the result will be all such edits after the current edit, then the map-channel result applied to the original (not the newly edited) data. That is,
(let ((first #t)) 
  (map-channel (lambda (y) 
                 (if first (set! (sample 0) 1.0)) 
                 (set! first #f) 
                 (* y 2))))
will return with two edits registered in the edit history list; the map-channel result will be the original data doubled; the preceding edit in the list will be the (set! (sample 0) 1.0) which the map-channel ignores.
maxamp (:optional snd chn edpos)
This returns the max amp of the given channel. Used with set!, it is equivalent to scale-to.
(define (maxamp-all)
  "(maxamp-all) returns the current maxamp of all currently open sounds"
  (apply max (map (lambda (snd) (apply max (maxamp snd #t))) (sounds))))

Sound file maxamp: mus-sound-maxamp
Region maxamp: region-maxamp
Selection maxamp: selection-maxamp
Sound data object maxamp: sound-data-maxamp
Vct maxamp: vct-peak
To set the y axis bounds to reflect the channel's maxamp: y-bounds
Mix maxamp: mix-maxamp
maxamp locations: maxamp-position, region-maxamp-position, selection-maxamp-position

maxamp-position (:optional snd chn edpos)
This gives the location (sample number) of the maximum sample in the given channel.
max-transform-peaks (:optional snd chn)
This returns the maximum number of transform peaks reported. The default is 100. max-transform-peaks affects both the fft display (if show-transform-peaks) and the peaks function.
min-dB (:optional snd chn)
This sets the minimum dB value displayed in various graphs (the default is -60.0). Due to problems with arithmetic underflows in sqrt, the spectrum functions set the lowest actual dB value calculated to -140.0 or -180.0 (depending on which function is called and so on).
new-sound (&optional-key :file :header-type :data-format :srate :channels :comment :size)
new-sound creates a new sound named 'file'. The following function opens a new sound named "test.snd", extends it to 'dur' samples, and initializes all samples to 'val':
    (define (init-sound val dur)
      (let ((ind (new-sound "test.snd" :size dur)))
        (map-channel (lambda (y) val))
        ind))
If the 'header-type' and other arguments are not specified, they default to the current default-output-header-type and related settings. Data formats are (b=big-endian, l=little, u=unsigned):
    mus-bshort  mus-lshort mus-mulaw  mus-alaw   mus-byte   mus-ubyte   mus-bfloat
    mus-lfloat  mus-bint   mus-lint   mus-b24int mus-l24int mus-bdouble mus-ldouble
    mus-ubshort mus-ulshort
Header-types are:
    mus-next mus-aifc mus-riff mus-rf64 mus-nist mus-raw mus-ircam mus-aiff 
    mus-soundfont mus-bicsf mus-voc mus-svx mus-caff
To be informed whenever a new sound is created, use new-sound-hook (see ws.scm).
normalize-channel (norm :optional beg dur snd chn edpos)
normalize-channel scales (changes the amplitude) of a sound so that its new peak amplitude is 'norm'. This is the "regularized" form of scale-to. The multichannel version is normalize-sound in extensions.scm.
open-raw-sound (&optional-key :file :channels :srate :data-format)
This opens 'file' as a raw (no header) sound in the layout specified. If the file has a header, it is not ignored (use (set! (data-format ...)) and friends to get around this). If the header is messed up, you can override its settings by giving the correct values in the call to open-raw-sound.
(define mpg
  (lambda (mpgfile rawfile chans)
    "(mpg file tmpname chans) converts file from MPEG-3 to raw 16-bit samples using mpg123"
    (system (format #f "mpg123 -s ~A > ~A" mpgfile rawfile))
    (open-raw-sound rawfile 1 44100 (if (little-endian?) mus-lshort mus-bshort))))
There's a more elaborate version of this function in examp.scm. See also open-raw-sound-hook.
open-sound (filename)
open-sound opens 'filename' and returns its index; this is equivalent to the File:Open option. view-sound opens a sound read-only, or you can set read-only by hand. close-sound closes a file opened by open-sound. There are a variety of hooks that are invoked during the sound opening process: during-open-hook, open-hook, after-open-hook, initial-graph-hook, open-raw-sound-hook. The sequence of actions is:
    bad header?: bad-header-hook — can cancel request
    no header?:  open-raw-sound-hook — can cancel request
    file ok: 
         open-hook — can change filename
         file opened (no data read yet)
             during-open-hook (can set prescaling etc)
         data read, no graphics yet
         after-open-hook
         initial-graph-hook
There are other ways to get at sound file data: make-sample-reader can be given a filename, rather than a sound index; file->vct in examp.scm; mus-sound-open-input and there are a variety of CLM-based functions such as file->sample and file->array.
pad-channel (beg dur :optional snd chn edpos)
pad-channel inserts 'dur' zeros at 'beg' in the given channel. This is the regularized version of insert-silence. To set a block of samples to zero, use scale-channel with a scaler of 0.0. To insert a block of arbitrary-valued samples:
(define* (block-channel value :optional beg dur snd chn edpos)
  (let ((val value))                    ; this for the run macro's benefit (in Guile)
    (pad-channel beg dur snd chn edpos) ; insert 'dur' samples, ptree-channel sets their values
    (ptree-channel (lambda (y) val) beg dur snd chn)))
We could also use map-channel here (rather than ptree-channel), but this version uses only virtual edits, so no matter how big the block of samples we insert, no disk space or memory is needed. The multichannel version is pad-sound in frame.scm.
pausing ()
pausing is #t if sound output is currently paused. You can unpause the sound by setting pausing to #f, and pause it by setting pausing to #t. If you pause a sound (via C-click of the play button, for example), then call play (via a key binding perhaps), the sound remains paused by default. To cancel the current pause and restart with the new play command:
(bind-key (char->integer #\p) 0
  (lambda ()
    (if (pausing) (stop-playing))
    (play)))
peak-env-info (:optional snd chn pos)
This returns some of the overall amplitude envelope data for the given channel at the given edit list position. The data currently returned are whether the envelopes are complete (they are the result of a background process), and the min and max data values. This is aimed at initial graph setup code that wants to fit the graph bounds to the data. See initial-graph-hook and peak-env.scm. The complete peak-env graphs are returned by channel-amp-envs.
peaks (:optional file snd chn)
peaks displays fft peak information. If 'file' is not null, it writes the information to that file, otherwise it posts the data in a help window (where it can be selected and pasted elsewhere). The maximum number of peaks reported is set by max-transform-peaks.
(add-hook! after-transform-hook (lambda (a b c) 
                                  (peaks))) ; post a detailed list of peaks after each FFT
play (:optional samp snd chn sync end edpos stop-proc out-chan)
This function plays the given channel starting from sample 'samp'. If 'sync' is #t, it plays all sounds sync'd to 'snd'. If 'end' is not given or is #f, it plays until the end of the sound. If 'end' is given (as a sample number), the actual end point may be off by a few samples; Snd only checks on dac-buffer boundaries (normally around 256 samples).
    (play)                                     ; play current sound, all chans from start to end
    (play (cursor))                            ; play starting from the cursor
    (play (round (* 3.0 (srate))) 1 3)         ; play snd 1, chan 3 (4th chan), start at 3.0 secs
    (play 0 #f #f #t)                          ; play sync'd sounds
    (play 0 #f #f #f (round (* 3.0 (srate))))  ; play from start for 3.0 secs
    (play 0 #f #f #f #f 2)                     ; play the version at edit history position 2
    (play 0 0 2 #f #f #f #f 0)                     ; play chan 2, but send it to DAC chan 0
    (play (mark-sample 0) #f #f #f (mark-sample 1)); play between marks 0 and 1
    (play (selection-position) #f #f (+ (selection-position) (selection-frames))) ; play selection
'samp' can also be a filename (a string). In this case, 'snd' can be the start sample (default 0), and 'chn' can be the end sample (default end-of-file).
    (play "1a.snd")                                ; play 1a.snd
    (play "1a.snd" 1000 4000)                      ; play 1a.snd from sample 1000 to 4000
If 'chn' is not given, or is a boolean, play plays all channels together. If 'pos' is given, it plays at that edit position. If 'stop-proc' is a procedure of one argument, it is called when the play process stops. The argument passed to 'stop-proc' provides the reason the play is stopping; it will be 0 if the play completed normally. 'stop-proc' is intended mainly for looping plays, as in play-often.
    (play 0 #f #f #f #f #f 
      (lambda (reason)                             ; if interrupted, say so in the listener
        (if (not (= reason 0))
            (snd-print ";play interrupted"))))
The 'pos' argument makes it easier to try "A:B" comparisons; this plays the version before the latest edit:
    (play 0 #f #f #f #f (- (edit-position) 1))
The following code binds the "p" key to play all channels of the current sound from the cursor, and the "P" key to play the previous version of the current sound:
(define (play-from-cursor current)
  (play (cursor) #f #f #f #f (if current #f (- (edit-position) 1))))

(bind-key (char->integer #\p) 0 
  (lambda () "play from cursor" (play-from-cursor #t) keyboard-no-action))

(bind-key (char->integer #\P) 0 
  (lambda () "play previous version from cursor" (play-from-cursor #f) keyboard-no-action))
And here we play from the cursor with a moving ("tracking") cursor:
(define (pfc)
  (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))))
Finally, if 'samp' is a function, it is called on every sample; if it returns a number, that number is sent to the DAC; if it returns #f, it stops. play-mixes uses this function option to time the playing each mix in a sequence of mixes. Another example is play-sine:
(define (play-sine freq amp)
  "(play-sine freq amp) plays a 1 second sinewave at freq and amp"
  (let* ((len 22050)
	 (osc (make-oscil freq)))
    (play (lambda ()
	    (set! len (- len 1))
	    (if (<= len 0)        ; we've sent 22050 samples, so it's time to stop
		#f
		(* amp (oscil osc)))))))
play one channel: play-channel, play button in control panel or files dialog
play from cursor: C-q and example above
play from cursor with tracking cursor: pfc above
play the selection: play-selection, C-x p
play a region: play-region, C-x p, play button in Region dialog
play a mix: play-mix, play button in Mix dialog
play a sequence of mixes: play-mixes
play from mark: click or drag triangle (control-click for all chans)
play continuously between two marks: loop-it
stop playing: C-g, C-t, stop-playing, set playing to #f
pause or resume playback: space, set pausing
play repeatedly: play-often
play repeatedly until C-g: play-until-c-g
play region repeatedly: play-region-forever
play a file upon a keystroke: bind-key
play using an external program: (system "sndplay wood16.wav")
play a sine-wave or spectrum: play-sine and play-sines
play arbitrary mixtures of things: make-player and related functions, play-syncd-marks
send arbitrary data to the DAC: mus-audio-write, start-dac
play after sending the data through some function: play-sound
play with applied amplitude envelope: play-with-envs, play-panned
play an external file: (play "file")

The "reasons" that might be passed to the stop-procedure are:

    0    play completed normally	
    1    file is being closed	
    2    play button unset
    3    stop-playing function called	
    4    C-g	
    5    DAC error (no available channel)
    6    play error (audio setup problem)	
    7    apply requested (control panel)	
    8    file edited
    9    C-t
The hooks called during a play operation are:
    when a play request occurs: start-playing-hook — can cancel the request, 
                                also start-playing-selection-hook
        (any number of sounds can be playing at once)
    as each buffer is sent to the audio device: play-hook and dac-hook
    as each sound ends: stop-playing-hook, stop-playing-selection-hook
    close audio device: stop-dac-hook
play-and-wait (:optional samp snd chn sync end edpos stop-proc out-chan)
This plays the given channel starting from sample 'samp' and waits for it to finish. play on the other hand returns immediately so subsequent calls on play mix multiple sample streams together, rather than playing them one after the other. If 'pos' is given, play-and-wait plays at that edit position. 'stop-proc' is explained under 'play' above; it's not really needed here, but "consistency is a virtue", I guess.
play-channel (:optional beg dur snd chn edpos stop-proc out-chan)
play-channel is the regularized version of play.
player-home (player)
This returns a list of the sound index and channel number associated with player.
playing ()
This returns #t if sound output is currently in progress. You can also start playing by setting playing to #t (equivalent to calling start-playing with default arguments), and stop by setting it to #f (equivalent to stop-playing). playing only notices Snd-instigated "play" processes; mus-audio-open-output is invisible to it.
players ()
This returns a list of currently active players.
player? (obj)
This returns #t if 'obj' is an active player.
position->x (xpos snd chn axis)
This returns the x axis value that corresponds to the graph (screen pixel) position 'xpos'. To find the sample that the mouse is pointing at, given the current mouse position,
    (inexact->exact (round (* (srate snd) (position->x x snd chn))))
See gui.scm for examples.
position->y (ypos snd chn axis)
This returns the y axis value that corresponds to the graph (screen pixel) position 'ypos'. See gui.scm for examples.
progress-report (pct :optional snd chn)
The functions start-progress-report, progress-report, and finish-progress-report handle the animated hour-glass icon that hopes to amuse the idle user while some long computation is in progress. The 'pct' argument is a float between 0.0 and 1.0 which indicates how far along we are in the computation; there are only 20 separate icons, so there's no point in calling this more often than that. start-progress-report posts the initial icon, and finish-progress-report removes it. If the icons are not available, a message is posted in the sound's minibuffer using 'name' to identify itself. See ladspa.scm.
prompt-in-minibuffer (msg :optional callback snd raw)
This posts 'msg' in the sound's minibuffer and when you respond, it calls 'callback' with the response as the callback's argument. If 'callback' is specified it should be either #f or a function of one argument: the raw response if 'raw' is #t, otherwise the evaluated response. For example, the following fragment asks for a yes-or-no response, then takes some action:
  (define* (yes-or-no question action-if-yes action-if-no :optional snd)
    (prompt-in-minibuffer question
		 	  (lambda (response)
			    (clear-minibuffer)
			    (if (string=? response "yes")
			        (action-if-yes snd)
			        (action-if-no snd)))
			  snd #t))
See eval-over-selection in extensions.scm for a more useful example. We could also use a continuation here:
(define (prompt msg default)
  (call-with-current-continuation
   (lambda (rsvp)
     (prompt-in-minibuffer msg rsvp)
     default)))
The 'raw' argument is useful when we want to prompt for yes or no, without forcing the user to put the answer in double quotes. In the next example, we replace Snd's built-in C-x k action (which immediately closes the sound) with one that is more like Emacs (it prompts for confirmation first):
(bind-key (char->integer #\k) 0 
  (lambda ()
    "close sound"
    (clear-minibuffer)
    (prompt-in-minibuffer
     (format #f "close ~S (cr=yes)?" (short-file-name))
     (lambda (response)
       (if (and (not (c-g?)) ; C-g => no
		(or (not (string? response))
		    (= (string-length response) 0)
		    (char=? (string-ref response 0) #\y)))
	   (close-sound)))
     #f   ; selected sound
     #t)) ; treat as string (i.e. don't require double quotes)
  #t)     ; C-x ...
ptree-channel (proc :optional beg dur snd chn edpos env-too init-func origin)
ptree-channel applies the function 'proc' as a 'virtual edit'; that is, the effect of 'proc' comes about as an implicit change in the way the data is read.

To be less Orphic: all the data accesses in Snd go through the edit-history list. The currently active member of that list chooses a channel data accessor based on the type of edit. A multiply by 2, for example, does not multiply anything by 2 internally; it just chooses the accessor that multiplies by 2 on any read. Since every other data access goes through this reader, from any other point of view, the data has been multiplied. These accessors make it unnecessary to save any data in temp files or internal arrays, and editing the edit-history list is very fast — we just tack a new accessor choice onto the edit-history list, so the edit operation appears to be instantaneous and memory-less. Lots of other operations are already being done this way in Snd (deletions, scaling, most envelopes, some channel swaps, etc). ptree-channel extends the idea to (nearly) arbitrary functions. When you call
    (ptree-channel (lambda (y) (* y 2)))
which has the same effect as
    (map-channel (lambda (y) (* y 2)))
the optimizer makes the parse-tree that represents (lambda (y) (* y 2)), then the data accessor system uses that parse-tree every time the data is read.

If the argument 'env-too' is #t, the same function is applied to the peak env values to get the new version of the peak env data. The default is #f, and should be #t only if the old max and min values as processed through 'proc' will be the new max and min values. Snd uses the peak env values when the graph of the sound covers very large amounts of data. If 'env-too' is #f, a background process is launched reading all the sound data through 'proc'; this can be time-consuming, so if you're viewing a half-hour of sound data, it can take awhile for the ptree-channel results to be displayed.

Here is a simple example that adds a constant to each sample in a file.
    (define* (offset-channel dc :optional (beg 0) dur snd chn edpos)
      (ptree-channel (lambda (y) (+ y dc)) beg dur snd chn edpos #t))
The actual edit that takes place is (+ y dc); that is, 'dc' is added to every sample. As subsequent editing takes place, the entries representing the 'offset-channel' call can become thoroughly fragmented. If your editing operation has any state (i.e. needs to know where it is in the data), you need to add an 'init-func' (the 8th argument to ptree-channel). The 'init-func' takes two arguments, the begin time of the read operation, relative to the original start of the fragment, and the original fragment duration (both in samples). It should return the information the main function ('proc' above) needs to handle the current fragment correctly. Global variables are not guaranteed to be set within the body of 'proc', so use a vct for local values that change as the read operation progresses.

The other new argument to 'proc' is the read direction; the read operations can change direction at any time, and any ptree-channel function needs to know how to deal with that. So, in the complicated, 3 argument case, the sequence of operations is: a read is requested, 'init-func' is called with the read start point (relative to the original segment start), it returns any state that 'proc' may need to refer to, then each time a sample is needed from the current sample reader, 'proc' is called passing it the current underlying sample, the return value of 'init-func', and the read direction. Here is an example that mixes a sine wave into the current channel:
(define* (sine-channel freq amp :optional (beg 0) dur snd chn edpos)
  (ptree-channel
   (lambda (y data forward)
     (let* ((angle (vct-ref data 0))
	    (incr (vct-ref data 1))
	    (val (+ y (* (vct-ref data 2) (sin angle)))))
       (if forward
	   (vct-set! data 0 (+ angle incr))
	   (vct-set! data 0 (- angle incr)))
       val))
   beg dur snd chn edpos #f
   (lambda (frag-beg frag-dur)
     (let ((incr (/ (* 2 pi freq) (srate))))
       (vct (fmod (* frag-beg incr) (* 2 pi)) incr amp))))) ; fmod from C
In the normal case, this function simply mixes in a sine wave: (+ y (* (vct-ref data 2) (sin angle))) where the amplitude scaler is stored in (vct-ref data 2). In subsequent reads, the init-func sets up a vct with the current phase (dependent on the frequency and the fragment begin sample), the phase increment (dependent on the frequency), and the amplitude (passed as an argument to sine-channel, but stored in the vct since the outer function's arguments won't be accessible in the main function ('proc')). See 'sine-ramp' in extensions.scm for another example.

In our 'sine-channel' function, we passed #f as the 'env-too' argument, to make sure Snd doesn't blithely apply the sine mix to the peak amplitude envelopes. When 'env-too' is #t, 'proc' is evaluated over the peak env data, rather than the original data. This makes redisplay much faster whenever a lot of data is being displayed, but only works if the function's output at the peak env min and max values are still the min and max values in the actual data (this is the case in the sinusoidal envelope, sine-ramp, mentioned above). When 'proc' is being called to calculate the new peak env, the duration passed to the init-func is the envelope size. Here is a version of cosine-channel given under map-channel that can handle peak-envs:
(define* (cosine-channel-via-ptree :optional (beg 0) dur snd chn edpos)
  ;; vct: angle increment
  (ptree-channel
   (lambda (y data forward)
     (let* ((angle (vct-ref data 0))
	    (incr (vct-ref data 1))
	    (val (* y (cos angle))))
       (if forward
	   (vct-set! data 0 (+ angle incr))
	   (vct-set! data 0 (- angle incr)))
       val))
   beg dur snd chn edpos #t
   (lambda (frag-beg frag-dur)
     (let ((incr (/ pi frag-dur)))
       (vct (+ (* -0.5 pi) (* frag-beg incr))
	    incr)))))
If the underlying data has too many previous ptree operations, map-channel is called instead and the new data is saved in the normal manner (that is, I don't currently try to chain any number of these operations together).

If no 'init-func' is specified, the editing procedure ('proc') should not assume anything about the context in which it is called; in this case, there's no way for 'proc' to know where it starts, or when it is being restarted, or which direction it is running, so, the following call:
   (let ((ctr 0)) 
     (ptree-channel (lambda (y) 
                      (set! ctr (+ 1 ctr)) 
                      (* ctr .0001))))
will never reset ctr to 0! Every time a portion of the data is read by Snd, the samples will be higher. But, the notion of an accessor that returns a different thing each time a sample is accessed is not foolish:
(define* (dither-channel :optional (amount .00006) beg dur snd chn edpos)
  (let ((dither (* .5 amount)))
    (ptree-channel 
      (lambda (y) 
        (+ y (mus-random dither) (mus-random dither))) 
      beg dur snd chn edpos #t)))
This gives a slightly different take on the sound each time you look at it or listen to it; the dithering is never exactly the same. But if the details of the noise don't matter (presumably the case with dithering), the difference is unproblematic. You're editing a sort of mobile in sound (analogous to mobile sculpture).

One major limitation of ptree-channel with an 'init-func' is that save-state currently doesn't know how to save the enclosing environment along with the init-func. So,
  (let ((outer 0.5))
    (ptree-channel (lambda (y data forward)
 		     (declare (y real) (data float) (forward boolean))
		     (* y data))
		   0 #f ind 0 #f #f
		   (lambda (pos dur)
		     outer)))
will not save the "outer" declaration in the saved state file. This is a general problem with save-state; there's no obvious way in Guile to save the current closure as text. You can fix the saved state file by hand (it is just Scheme, Ruby, or Forth code, of course), but that's not a very elegant solution.

The real limitation in using ptree-channel, however, arises from the fact that the read direction can not only be backwards, but it can also change at any time. In conjunction with the fragmentation during editing, this makes it hard to use CLM generators, or anything that depends on previous samples. Since the run macro (on which ptree-channel depends) is currently limited in the kinds of vector or list elements it can decipher, you're pretty tightly constricted in this context. The read direction argument can be ignored if you know you're not going to read backwards. The only hidden reverse read is in the src generator where a negative increment can be generated in a variety of ways (for example, src driven by oscil). A one zero filter could in this case be:
  (ptree-channel (lambda (y data forward)
		   (let ((val (* 0.5 (+ y (vct-ref data 0)))))
		     (vct-set! data 0 y)
		     val))
		 0 (frames) ind 0 #f #f ; "ind" is the sound index 
		 (let ((edpos (edit-position ind 0)))
		   (lambda (pos dur)
		     (vct (if (= pos 0) 0.0
			      (sample (- pos 1) ind 0 edpos))))))
See also virtual-filter-channel in snd-test.scm; it uses the optional third argument to the init function to set up the filter state correctly, and has code to run the filter backwards if necessary. Here are several more examples:
(define* (ptree-scale scl :optional (beg 0) dur snd chn edpos)
  "ptree-channel version of scale-channel"
  (ptree-channel
    (lambda (y) 
      (* scl y)) 
    beg dur snd chn edpos #t))

(define* (ptree-xramp r0 r1 ubase :optional (beg 0) dur snd chn edpos)
  "exponential version of ramp-channel"
  ;; this is essentially what CLM's exponential envelope generator is doing
  ;;   to accommodate C, it uses (exp (* power (log base))) prescaling power by (log base)
  (let* ((base (if (> r0 r1) (/ 1.0 ubase) ubase)))
    (ptree-channel
     (lambda (y data forward)
       (let* ((lr0 (vct-ref data 0))
	      (lbase (vct-ref data 1))
	      (incr (vct-ref data 2))
	      (scl (vct-ref data 3))
	      (power (vct-ref data 4))
	      (val (* y (+ lr0 (* scl (- (expt lbase power) 1.0))))))
	 (if forward
	     (vct-set! data 4 (+ power incr))
	     (vct-set! data 4 (- power incr)))
	 val))
     beg dur snd chn edpos #t
     (lambda (frag-beg frag-dur)
       ;; r0, base, incr, (/ (- r1 r0) (- base 1.0)), current power
       (vct r0
	    base
	    (/ 1.0 frag-dur)
	    (/ (- r1 r0) (- base 1.0))
	    (/ frag-beg frag-dur))))))

(define* (virtual-mix file beg :optional snd chn)
  (let ((len (mus-sound-frames file)))
    (ptree-channel
     (lambda (y state forward)
       (declare (y real) (state sample-reader) (forward boolean))
       (+ y (if forward (next-sample state) (previous-sample state))))
     beg len snd chn -1 #f
     (lambda (frag-beg frag-dur)
       (make-sample-reader frag-beg file)))))

To handle an envelope in a ptree-channel application, it's probably easiest to split it up into a sequence of ramps, each ramp keeping track of its local begin point and increment. This is how sine-env-channel works, not to mention the built-in linear and exponential envelopes. The underlying procedure is any-env-channel which needs only the segment connecting function, and handles the rest itself. Here is an implementation of Anders Vinjar's power-envelope (a list of breakpoints with an added base for each segment), that splits the envelope into a sequence of xramp-channel calls. It also uses as-one-edit to make the result appear to be one operation in the edit history list.
(define* (powenv-channel envelope :optional (beg 0) dur snd chn edpos)
  ;; envelope with a separate base for each segment: 
  ;;    (powenv-channel '(0 0 .325  1 1 32.0 2 0 32.0))
  (let* ((curbeg beg)
	 (fulldur (or dur (frames snd chn edpos)))
	 (len (length envelope))
	 (x1 (car envelope))
	 (xrange (- (list-ref envelope (- len 3)) x1))
	 (y1 (cadr envelope))
	 (base (caddr envelope))
	 (x0 0.0)
	 (y0 0.0))
    (if (= len 3)
	(scale-channel y1 beg dur snd chn edpos)
	(as-one-edit
	 (lambda ()
	   (do ((i 3 (+ i 3)))
	       ((= i len))
	     (set! x0 x1)
	     (set! y0 y1)
	     (set! x1 (list-ref envelope i))
	     (set! y1 (list-ref envelope (+ i 1)))
	     (let* ((curdur (inexact->exact (round (* fulldur (/ (- x1 x0) xrange))))))
	       (xramp-channel y0 y1 base curbeg curdur snd chn edpos)
	       (set! curbeg (+ curbeg curdur)))
	     (set! base (list-ref envelope (+ i 2)))))))))
If the init-func returns something other than a vct, you need to declare its type for the run macro. There are several examples in snd-test.scm.

more ptree-channel examples:
smoothing: smooth-channel-via-ptree (examp.scm)
compander: compand-channel (examp.scm)
insert a block of arbitrary-valued samples: block-channel
sinusoidal ramp: sine-ramp (extensions.scm)
sinusoidal envelope: sine-env-channel (extensions.scm)
CLM-style contrast-enhancement: contrast-channel (extensions.scm)
add a constant to every sample: offset-channel (extensions.scm)
ring modulation: ring-modulate-channel (examp.scm)
delay by n samples (an experiment!): delay-channel in extensions.scm
dithering: dither-channel (extensions.scm)
FIR filter: virtual-filter-channel (examp.scm)

ramp-channel (rmp0 rmp1 :optional beg dur snd chn edpos)
ramp-channel is a slight extension of scale-channel. It scales samples in the given sound/channel between 'beg' and 'beg' + 'dur' by a (linear) ramp going from 'rmp0' to 'rmp1'.
read-only (:optional snd)
This returns #t if 'snd' is read-only, #f otherwise. If you open a file with view-sound, read-only is set to #t. read-only does not reflect (or affect) the write permission state of the underlying file; it is a way to keep from accidentally clobbering an otherwise writable file. If it is #t (or if the file is not writable), a lock icon is displayed beside the file name.
read-peak-env-info-file (snd chn filename)
This opens and reads the data in 'filename', assumed to be the peak-env amp info written by write-peak-env-info-file for the given channel. It expects to be called within initial-graph-hook. See peak-env.scm.
redo (:optional edits snd chn)
This re-activates 'edits' edits (the default is 1) in the given channel. Redo follows the sync field if it is not 0. The following might be a more reasonable redo function:
(define* (redo-channel :optional (edits 1) snd chn)
  (if (and snd (not (= (sync snd) 0)) chn)
      (set! (edit-position snd chn) (+ (edit-position snd chn) edits))
      (redo edits snd)))
redo moves forward in the edit history list, whereas undo backs up, and revert-sound resets the current edit position to the start of the list. For more about the edit history list, see Edit Lists.

In Ruby, redo is a part of the loop handling, so Snd's redo is renamed redo_edit. redo-edit also exists in Scheme, for consistency.
report-in-minibuffer (msg :optional snd as-error)
This posts 'msg' in the sound's minibuffer. The 'minibuffer' is the text widget between the sound's filename and the buttons on the right, beneath the graph. It is intended to mimic Emacs' minibuffer, being useful mainly for short, temporary messages. C-g clears it, as does clear-minibuffer. If 'as-error' is #t, the message is placed in the minibuffer error label, rather than in the usual text area. See also prompt-in-minibuffer.
reverse-channel (:optional beg dur snd chn edpos)
reverse-channel is the regularized version of reverse-sound.

reverse selected portion: reverse-selection
read samples in reverse: use make-sample-reader with direction -1
reverse at new sampling rate: use src-channel with a negative ratio
Reverse in control panel: control panel and speed-control variable
reverse an envelope: reverse-envelope
reverse block-wise: reverse-by-blocks and reverse-within-blocks
reverse via FFT: silly-reverse
reverse order of channels: reverse-channels
reverse a list: reverse and reverse!
reverse a string: in Guile or Forth: string-reverse, in Ruby: reverse
reverse a vct: vct-reverse!
reverse a frame: frame-reverse

reverse-sound (:optional snd chn edpos)
reverse-sound reverses the sound data in the given channel. There are some interesting non-causal effects you can get with this: take a voice sound, reverse it, reverberate it, reverse it again, and you get the original with reversed reverb. As a hack, you can reverse a sound (modulo a one sample rotation) by doing two ffts (DSP-ers call this a "flip"):
(define* (silly-reverse :optional snd)
  (let* ((len (frames snd 0))
	 (fsize (expt 2 (ceiling (/ (log len) (log 2)))))
	 (rl (channel->vct 0 fsize snd 0))
	 (im (make-vct fsize)))
    (mus-fft rl im fsize)
    (mus-fft rl im fsize)
    (vct-scale! rl (/ 1.0 fsize))
    (vct->channel (vct-subseq rl (- fsize len) fsize) 0 len snd 0)))
revert-sound (:optional snd)
This reverts 'snd' to its saved (unedited) state. A channel-specific version:
(define* (revert-channel :optional snd chn)
  (set! (edit-position snd chn) 0))
right-sample (:optional snd chn)
This returns the position (in samples) of right edge of the time domain waveform. See left-sample, move-one-pixel, and many examples in examp.scm.
run (thunk)
run is the Snd equivalent of the CL/CLM run macro. You can wrap it around any numerically-intensive block of code, and the result will usually run 10 to 20 times faster. In the context of with-sound, run is used to speed up instrument bodies. My timing tests indicate that Snd+Run instruments are within a factor of two to four of the speed of CL+run+C in CLM. Currently, only S7 and Guile support this optimization.
(define (ws-sine freq)
  (let ((o (make-oscil freq)))
    (run
     (lambda ()
       (do ((i 0 (+ 1 i)))
	   ((= i 100))
	 (outa i (oscil o)))))))
Functions embedded within run may need to declare the type of their arguments; run assumes each variable has one type (integer by default) throughout its life. So, the following code displays "0", rather than "3.14":
  (run (lambda () (let ((x 3.14)) (define (a b) (display b)) (a x))))
The "b" argument to "a" is assumed to be an integer, and passing in a float causes nothing but confusion. To get this code to work right:
  (run (lambda () (let ((x 3.14)) (define (a b) (declare (b real)) (display b)) (a x))))
The current declarable types include any def-clm-struct type and:
    int float boolean char string list symbol keyword vct sample-reader mix-sample-reader 
    sound-data clm float-vector int-vector vct-vector list-vector clm-vector 
declare is modelled after Common Lisp's declare; it is specific to run.

The use of the run macro is hidden in many contexts: map-channel, find-channel, etc. Internally the Snd run macro uses 64-bit ints and doubles, so large sounds should not present any numerical problems. See optimization for some timings. In Ruby, it's possible to use the RubyInline module instead.
sample (:optional samp snd chn edpos)
This function gives the value of sample 'samp' in the given channel.
  Scheme: (set! (sample 100) .5)

  Ruby:   set_sample(100, 0.5)

  Forth:  100 0.5 set-sample
If the desired sample happens to fall outside the current buffer for the indicated channel, sample grinds to a halt — if you're running a loop through a bunch of samples, use the sample-readers or channel->vct instead. 'samp' defaults to the current cursor location.
samples (:optional samp samps snd chn edpos)
This returns a vct of 'samps' samples starting at 'samp' in the given channel. 'samp' defaults to 0. 'samps' defaults to frames - 'samp' (i.e. read to the end of the data). 'pos' is the edit history position to read (it defaults to the current position). This is settable (as is sample):
    :(samples 1000 10)
    #<vct[len=10]: 0.033 0.035 0.034 0.031 0.026 0.020 0.013 0.009 0.005 0.004>
    :(set! (samples 1000 10) (make-vct 10 .1))
    #<vct[len=10]: 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100>
save-sound (:optional snd)
save-sound saves 'snd', writing the current state of the sound to its underlying sound file, (like the File menu's Save option). save-hook is invoked upon save-sound. After save-sound, the sound has no undoable edits in its edit history (this is different from Emacs, but I find Emac's way of handling this very confusing, and it's never what I want).
save all sounds: (for-each save-sound (sounds))
save a sound under a different name: save-sound-as
extract one channel from a sound: extract-channel
extract a set of channels from a sound: extract-channels
save a sound in a different format or header: save-sound-as
backup edits automatically: autosave
check first for unsaved edits: check-for-unsaved-edits
save Snd's complete state (unsaved edits and all): save-state, save-dir, save-state-hook, save-state-file
save the selection: save-selection
save a region: save-region
save a mix: save-mix
save the control panel state: save-controls
save currently defined envelopes (envelope editor): save-envelopes
start the file save dialog: save-sound-dialog
start the selection save dialog: save-selection-dialog
start the region save dialog: save-region-dialog
use Emacs-style save-as behavior: emacs-style-save-as
save the current listener text: save-listener
save keyboard macros: save-macros
save marks: save-marks
save just the edit history: save-edit-history
save the peak-env data: save-peak-env-info
take some action upon a window manager save-yourself signal: upon-save-yourself
save the current sound setup for a later reopen: remember-sound-state
save the current fft peak info: peaks
save-sound-as (&optional-key :file :sound :header-type :data-format :srate :channel :edit-position :comment)
This saves 'sound' as 'file' (like the 'File:Save as' menu option). If 'channel' is specified, only that channel is saved (it is extracted if necessary from the multichannel original). 'edit-position', if given, specifies which edit history position to save. The :srate argument refers only to the new sound file's header's srate field; the data is not resampled. If you want to resample the data as it is saved, see the example under before-save-as-hook. If :data-format is given, the sound file is written using that data format. Any omitted argument's value is taken from the sound being saved. save-sound-as returns the new file name.
    (save-sound-as "test.snd" :data-format mus-bdouble :header-type mus-aifc)
saves the currently selected sound as an AIFC file using big-endian doubles for the samples.

To start a parallel editing branch on a given file, you could:
    (save-sound-as "test.snd") (open-sound "test.snd")
To define an explicit channel extraction function:
    Scheme:
    (define (extract-channel filename snd chn) (save-sound-as filename snd :channel chn))

    Ruby:
    def extract_channel(filename, snd, chn) save_sound_as(filename, snd, :channel, chn) end

    Forth:
    : extract-channel { filename snd chn } filename snd :channel chn save-sound-as ;
The hooks called during a save operation are:
    before-save-as-hook — can cancel the request or set its output parameters
    save-hook
        sound saved
          if any sample is clipped during save, clip-hook
    after-save-as-hook
scale-by (scalers :optional snd chn)
scale-by scales the amplitude of 'snd' by 'scalers'. Unlike most of these functions, scale-by follows the 'sync' buttons and affects all currently sync'd channels. 'scalers' can be either a float, a list, or a vct. In the latter case, the values are used one by one, applying each as scale-by moves through the channels. If 'sync' is off, channel 'chn' is scaled (it defaults to the currently selected channel). (scale-by 2.0) doubles all samples.
scale-channel (scl :optional beg dur snd chn edpos)
scale-channel scales (changes the amplitude) of a sound by 'scl'. The multichannel version is scale-sound in frame.scm. channel-polynomial is a generalization of the idea. There are approximately a bazillion ways to scale samples in Snd; here's a potpourri of increasingly silly choices:
(scale-channel 2.0)
(scale-by 2.0)
(map-channel (lambda (val) (* val 2.0)))
(set! (maxamp) (* 2 (maxamp)))
(env-sound '(0 2 1 2))
(env-channel (make-env '(0 1 1 1) :scaler 2.0 :length (frames)))
(clm-channel (make-one-zero :a0 2.0 :a1 0.0))
(filter-channel (vct 2.0) 1)
(vct->channel (vct-scale! (channel->vct) 2.0) 0)
(sound-data->sound (sound-data* (sound->sound-data) 2.0))
(begin (select-all) (mix-selection 0))
(begin (select-all) (scale-selection-by 2.0))
(begin (save-sound-as "temp.snd") (mix "temp.snd" 0) (delete-file "temp.snd"))

(let ((flt (make-vct 8)))
  (vct-set! flt 0 2.0)
  (let ((cnv (make-convolve :filter flt))
	(sf (make-sample-reader 0)))
    (map-channel
     (lambda (val)
       (convolve cnv (lambda (dir) 
                       (read-sample sf)))))))

(vct->channel (poly* (channel->vct 0 (frames)) (vct 2.0))) ; poly.scm (sound = polynomial coeffs)

(let* ((len (frames))
       (fsize (expt 2 (ceiling (/ (log len) (log 2)))))
       (rl (channel->vct 0 fsize))
       (im (make-vct fsize)))
  (mus-fft rl im fsize)
  (mus-fft rl im fsize)
  (mus-fft rl im fsize)
  (mus-fft rl im fsize)
  (vct->channel (vct-scale! rl (/ 2.0 (* fsize fsize))) 0 len))

(do ((i 0 (+ 1 i)))
    ((= i (frames)))
  ;; don't actually do this! — it involves a separate edit on each sample
  (set! (sample i) (* 2 (sample i))))

(let ((make-scaler 
       (lambda (start end)
	 (letrec ((ctr start)
		  (us (lambda (them)
			(set! (sample ctr) (* 2.0 (sample ctr)))
			(set! ctr (+ ctr 2))
			(if (<= ctr end)
			    (them us)))))
	   us))))
  ((make-scaler 0 (frames)) 
     (make-scaler 1 (frames))))
scale-to (:optional norms snd chn)
scale-to normalizes 'snd' to 'norms' (following sync as in scale-by). (scale-to 0.5) scales the current channel so that its maxamp is 0.5. If all the sound's samples are 0.0, scale-to returns #f and does not perform any edit. 'norms' can be a number, a list of numbers, or a vct.
scan-chan (func :optional start end snd chn edpos)
scan-chan applies 'func' to samples in the specified channel. It is the old ("irregular") version of scan-channel.
scan-channel (func :optional beg dur snd chn edpos)
scan-channel "scans" the data in the specified channel between the given sample numbers (the default is the entire sound) by applying 'func' to each sample. If 'func' returns something other than #f, the scan is halted, and a list is returned containing that value and the current sample position of the scan. The following call scans the current channel from sample 0 to the end looking for any sample greater than .1:
>(scan-channel (lambda (y) (> y .1)))
(#t 4423)
In this case, we found such a sample at position 4423.
(define every-sample?
  (lambda (proc)
    (let ((baddy (scan-channel (lambda (y) 
			         (not (proc y))))))
      (if baddy (set! (cursor) (cadr baddy)))
      (not baddy))))

>(every-sample? (lambda (y) (< y .5)))
#t
To scan all the channels of a multichannel sound in parallel, see scan-sound.

In scan-chan, scan-channel, find, and count-matches (all the same underlying procedure), an attempt to jump back into a previous call will not work. That is,
(let ((not-a-good-idea #f))
  (scan-channel (lambda (y)
                  (call-with-current-continuation
	            (lambda (call)
		      (set! not-a-good-idea call)))
		  (> y .001)))
  (not-a-good-idea))
will die with a segfault (this is fixable, with much effort and grumbling). If you want a continuable search, use a sample-reader:
(define reader #f)
(define last-proc #f)
(define (scan-again)
  (if (sample-reader-at-end? reader)
      #f
      (let ((val (last-proc (reader))))
	(if val 
	    (list val (- (sample-reader-position reader) 1))
	    (scan-again)))))
(define (my-scan-chan proc)
  (set! last-proc proc)
  (set! reader (make-sample-reader 0))
  (scan-again))
Now (my-scan-chan (lambda (y) (> y .1))) finds the first such sample, and subsequent (scan-again) calls continue the search where the last call left off.
search-procedure (:optional snd)
This gives the current global or sound-local (if 'snd' is specified) search procedure.
    (set! (search-procedure) (lambda (y) (> y .1)))
selected-channel (:optional snd)
This gives the selected channel in 'snd'; you can set it to select a channel. It returns #f is no channel is selected in 'snd'.
selected-sound ()
This gives the currently selected sound (its index, of course); you can set it to select a sound. It returns #f is there is no selected sound.
    (or (selected-sound)
        (and (not (null? (sounds)))
             (car (sounds))))
returns the currently selected sound, if any, and failing that, any other sound that is currently open.
select-channel (:optional chn)
This selects channel 'chn' in the currently selected sound; equivalent to (set! (selected-channel) chn). See also select-channel-hook.
select-sound (:optional snd)
This selects sound 'snd' (an index); equivalent to (set! (selected-sound) snd). See also select-sound-hook.
set-samples (samp samps data :optional snd chn trunc edname infile-chan edpos auto-delete)
set-samples (and its related (set! (samples...)...) form) set the given channel's samples starting from sample 'samp' for 'samps' samples to the values in 'data'.
    (set! (samples 0 100) (make-vct 100 .1))
    (set-samples 0 100 (make-vct 100 .1))

both change all samples between 0 and 100 to be 0.1. If 'samp' is beyond the end of the file, the file is first zero-padded to reach it. 'data' can be a filename.
    (set-samples 10000 20000 "oboe.snd")
replaces 10000 samples with data from oboe.snd. If 'data' is a vct, set-samples is identical to vct->channel. If 'trunc' is #t and 'samp' is 0, the sound is truncated (if necessary) to reflect the end of 'data'. If the in-coming data file has more than one channel, 'infile-chan' sets which input file to read. The in-coming data file is not deleted by Snd unless 'auto-delete' is #t. (If you write a temporary sound as an edit, it can be non-obvious when it is safe to delete that file; 'auto-delete' set to #t asks Snd to handle cleanup).

The form (set! (samples samp samps 'snd chn trunc edname infile-chan edpos auto-delete') data) can also be used. 'env-sound-interp' in examp.scm has an example of the file version, using sound-data objects and mus-sound-write to create a temporary file, but it's probably simpler to use with-sound (see also linear-src-channel in dsp.scm):
(define (step-src)
  (let* ((rd (make-sample-reader 0))
	 (o (make-oscil 2205.0))
	 (s (make-src :srate 0.0))
	 (incr (+ 2.0 (oscil o)))	  
	 (tempfile (with-sound (:output (snd-tempnam) :srate (srate) :to-snd #f)
		     (run (lambda ()
			    (do ((samp 0 (+ 1 samp)))
				((or (c-g?) (sample-reader-at-end? rd)))
			      (out-any samp (src s incr (lambda (dir) (read-sample rd))) 0)
			      (if (= (modulo samp 2205) 0)
				  (set! incr (+ 2.0 (oscil o)))))))))
	 (len (mus-sound-frames tempfile)))
    (set-samples 0 (- len 1) tempfile #f #f #t "step-src" 0 #f #t)))
short-file-name (:optional snd)
This returns the brief (no directory) form of the sound's filename.
    >(open-sound "oboe.snd")
    0
    >(file-name 0)
    "/home/bil/cl/oboe.snd"
    >(short-file-name 0)
    "oboe.snd"
show-axes (:optional snd chn)
This determines what axes are displayed. If show-axes is show-all-axes (the default), both the x and y axes are displayed; if it is show-x-axis, just one (bottom) x axis is displayed, reducing screen clutter. show-no-axes omits both x and y axes. To remove the x axis label, use either show-x-axis-unlabelled or show-all-axes-unlabelled. To omit all the x axis labels and ticks (but include the y axis as usual) use show-bare-x-axis. This is the View:Axes choice.
show-grid (:optional snd chn)
If show-grid is #t (the default is #f), a background grid is displayed (default is #f). See also grid-density.

grid
show-marks (:optional snd chn)
If show-marks is #t (the default), marks are displayed. This is the 'Show marks' View menu option.
show-mix-waveforms (:optional snd chn)
If show-mix-waveforms is #t (the default), a mixed sound is displayed as a separate waveform above the main data. The rectangular tag at the start of the waveform can be dragged to move the mix, or clicked to select it for the mix dialog.
show-sonogram-cursor (:optional snd chn)
If show-sonogram-cursor is #t (the default is #f), the cursor is also displayed in the sonogram.
show-transform-peaks (:optional snd chn)
If show-transform-peaks is #t (the default is #f), transform peak information is included in the transform display. This is the 'peaks' button in the Transform options dialog.
show-y-zero (:optional snd chn)
If show-y-zero is #t (the default is #f), the y=0 axis is displayed. This is the 'Show Y=0' View menu option.
smooth-channel (:optional beg dur snd chn edpos)
smooth-channel is the regularized version of smooth-sound. smooth-channel-via-ptree in examp.scm is the virtual form.
smooth all channels: smooth-sound
smooth selection: smooth-selection
delete the selection and smooth the splice: delete-selection-and-smooth
smoothing as virtual op: smooth-channel-via-ptree in examp.scm
smoothing via fft: fft-smoother
smooth via low-pass filter
smooth over click: remove-clicks in examp.scm
smooth-sound (beg num :optional snd chn)
smooth-sound applies a smoothing function to the indicated data. This produces a sinusoid between the end points:
(define (smoother y0 y1 num)
   "go sinusoidally from y0 to y1 over num samps"
   (let ((v (make-vct (+ 1 num))) 
	 (angle (if (> y1 y0) pi 0.0)) 
	 (off (* .5 (+ y0 y1))) 
	 (scale (* 0.5 (abs (- y1 y0)))))
     (do ((i 0 (+ 1 i)))
         ((= i num) v)
       (vct-set! v i 
         (+ off (* scale (cos (+ angle (* i (/ pi num))))))))))
smoother
For a fancier version, see fft-smoother in examp.scm. See also remove-clicks in examp.scm.
sound? (:optional snd)
sound? returns #t if 'snd' (an index) points to an open sound.
soundfont-info (:optional snd)
This returns a list of lists describing 'snd' as a soundfont. Each inner list consists of the sound name, start point, loop start, and loop end.
    :(soundfont-info)
    (("BrSteel_E4" 0 65390 65458) ("BrSteel_B2" 65490 131458 131637) ...)
To set a named mark at the start of each sound with un-named marks at the loop points:
(define (mark-sf2)
  (letrec 
    ((sf2it 
       (lambda (lst)
         (if (not (null? lst))
             (let* ((vals (car lst))
                    (m1 (add-mark (cadr vals))))
               (set! (mark-name m1) (car vals)))
               (add-mark (caddr vals))
               (add-mark (cadddr vals))
               (sf2it (cdr lst))))))
   (sf2it (soundfont-info))))
soundfont marks
See also explode-sf2 in examp.scm.
sound-loop-info (:optional snd)
This gives info about loop points from the sound's header. The loop info is a list of up to 4 points, the first two (start, end) refer to the sustain loop, the second two to the release. The 5th and 6th list entries are the base note and detune values. For historical reasons, the 7th and 8th entries are the sustain and release modes. This is similar to mus-sound-loop-info (but it's settable). See explode-sf2 in examp.scm.
    :(sound-loop-info)
    (24981 144332 0 0 60 0 1 0)
sound-properties (:optional snd)
This is a property list associated with the given sound. It is set to '() at the time a sound is opened. The accessor sound-property is provided in extensions.scm. There are several examples of using it in snd-motif.scm and autosave.scm.
sounds ()
sounds returns a list of currently active sounds (index numbers). A common Snd trope is (map func (sounds)):
    (map maxamp (sounds))
Or, if the return value is not needed:
    (for-each (lambda (snd) (display (short-file-name snd))) (sounds))
This can be extended to provide a complete list of sounds and channels (since many Snd functions take the "snd chn" arguments):
(define (all-chans)
  (let ((sndlist '())
        (chnlist '()))
    (for-each (lambda (snd)
                (do ((i (- (channels snd) 1) (- i 1)))
                    ((< i 0))
                  (set! sndlist (cons snd sndlist))
                  (set! chnlist (cons i chnlist))))
              (sounds))
    (list sndlist chnlist)))

(apply map maxamp (all-chans))
spectrum-end (:optional snd chn)
This is the amount of the frequency domain to include in the spectrum display (the default is 1.0 = all of it). spectrum-end the slider labelled '% of spectrum' in the View Orientation dialog. See zoom-fft in examp.scm.
spectro-hop (:optional snd chn)
This is the distance (in pixels) moved between successive spectrogram traces (default is 4). spectro-hop is the 'hop' slider in the Orientation dialog.
spectrum-start (:optional snd chn)
This is the start point of the frequency domain in the spectrum display (default is 0.0). See zoom-fft in examp.scm.
spectro-x-angle (:optional snd chn)
This is the spectrogram x-axis viewing angle (the default is 90.0 except in GL where it is 300.0). See snd-gl.scm.
spectro-x-scale (:optional snd chn)
This is the scaler (stretch amount) along the spectrogram x axis (the is default 1.0, in GL: 1.5).
spectro-y-angle (:optional snd chn)
This is the spectrogram y axis viewing angle (the default is 0.0, in GL: 320.0).
spectro-y-scale (:optional snd chn)
This is the scaler (stretch amount) for the spectrogram y axis (the default is 1.0).
spectro-z-angle (:optional snd chn)
This is the spectrogram viewing angle for the z axis (the default is 358.0, in GL: 0.0).
spectro-z-scale (:optional snd chn)
This is the scaler (stretch amount) for the z axis (the default is 0.1, in GL: 1.0).
squelch-update (:optional snd chn)
This is #t if graphic updates are currently squelched (turned off). If you're doing a sequence of edits where intermediate states aren't of great interest, you can save time by turning off redisplays.
(define (without-graphics thunk)
  (set! (squelch-update) #t)
  (let ((val (catch #t thunk (lambda args (car args)))))
    (set! (squelch-update) #f)
    val))
srate (:optional snd)
This is the sound's sampling rate. If you set this to a new value, update-sound is called to reflect the new srate, but any current edits are simply flushed. This is consistent with the other header fields (data-format, etc), but it can be annoying.

There are several srates floating around in Snd. (srate snd) returns the sampling rate of a particular (currently open) sound. (mus-sound-srate filename) returns a sound file's sampling rate. mus-srate is associated with the CLM package (setting the implicit srate for oscil etc). default-output-srate is the default sampling rate used when opening new files. enved-srate is a constant that can be assigned to the envelope editor's enved-target (to apply an envelope to the sampling rate). region-srate is the sampling rate associated with a region.
src-channel (num-or-env :optional beg dur snd chn edpos)
src-channel preforms sampling rate conversion using 'warped sinc interpolation'. The argument 'num-or-env' can be a number, an envelope, or a CLM env generator. (src-channel 2.0) makes the sound go twice as fast. This is the regularized version of src-sound.
    :(frames)                  ; current duration
    50828
    :(src-channel 2.0)         ; make it half as long
    2.0
    :(frames)
    25415
    :(src-channel '(0 .5 1 1)) ; start slow and speed up
    (0 0.5 1 1)
    :(frames)
    35235
    :(src-channel (make-env '(0 .5 1 1) :length 20000)) ; stick at 1 after sample 20000
    #<env linear, pass: 35236 (dur: 20000), index: 1, scaler: 1.0000, offset: 0.0000, ...>
    :(frames)
    42964
resample all chans: src-sound
resample selection: src-selection
resample mix: speed control in Mix dialog (also apply-controls)
resample via drawn envelope: srate in Envelope editor
resample via CLM gen: src
resample with independent time control (ssb-am): ssb-bank in dsp.scm
resample with independent time control (granulate): expand in control panel, expsrc and expsnd
resample with independent time control (vocoder): phase-vocoder (this never works)
another time stretcher (autocorrelation):rubber-sound (this takes forever and rarely works)
resampling-based sound effects: hello-dentist, fp ("Forbidden Planet"), flange and chorus in dsp.scm and new-effects.scm
the digital zipper: zipper
resample via FFT: down-oct
resample through env: sound-interp and env-sound-interp
resample through list: scratch
resample step-size through a function: step-src
predict duration of resampled sound: src-duration
linear src: linear-src-channel in dsp.scm

src-sound (num-or-env :optional base snd chn edpos)
src-sound performs sampling rate conversion using 'warped sinc interpolation'. The argument 'num-or-env', which sets the ratio between the old and the new srate, can be either a number or an envelope. In the latter case, 'base' sets the segment base (the default is 1.0 = linear). A value greater than 1.0 causes the sound to be transposed up. A value less than 0.0 causes the sound to be reversed. (src-sound 2.0) speeds the sound up by a factor of 2 (transposes it up an octave), whereas (src-sound 0.5) slows it down by the same factor (transposes it down an octave). (src-sound '(0 1 1 2)) starts at the original speed, then gradually increases until, at the end of the sound, it is going twice as fast.

'num-or-env' can also be a CLM env generator (its duration should be the same as the original sound, and its segments should not pass through 0.0). The following function can be used to predict how long the resultant note will be given an src envelope:
;;; find new duration of sound after using env as srate.
;;; the envelope gives the per-sample increment, so the "tempo"
;;;   is the inverse of that. To get the total new duration,
;;;   we need to integrate the inverse envelope, but a straight
;;;   line in the increment envelope becomes a 1/x curve in the
;;;   tempo curve, so we use log(x) as integral of 1/x and
;;;   take into account the local notion of "x".

(define (src-duration e)
  (let* ((len (length e))
	 (ex0 (car e))
	 (ex1 (list-ref e (- len 2)))
	 (all-x (- ex1 ex0))
	 (dur 0.0))
    (do ((i 0 (+ i 2)))
	((>= i (- len 2)) dur)
      (let* ((x0 (list-ref e i))
	     (x1 (list-ref e (+ i 2)))
	     (y0 (list-ref e (+ i 1))) ; 1/x x points
	     (y1 (list-ref e (+ i 3)))
	     (area (if (< (abs (- y0 y1)) .0001)
		       (/ (- x1 x0) (* y0 all-x))
		       (* (/ (- (log y1) (log y0)) 
			     (- y1 y0)) 
			  (/ (- x1 x0) all-x)))))
	(set! dur (+ dur (abs area)))))))

;;; (src-duration '(0 1 1 2)) -> 0.693147180559945
:;; (src-duration '(0 1 1 .5)) -> 1.38629436111989
;;; (src-duration '(0 .5 .5 3 .6 1 .7 .1 .8 1.5 1 1)) -> 1.02474349685432

;;; here we're using this in the Snd listener:
>(frames)
220501
>(src-duration '(0 1 1 2))
0.693147180559945
>(src-sound '(0 1 1 2)) ; should be about .693 * 220500 frames 
(0 1 1 2)
>(frames)
152842
>(/ 152842.0 220501)
0.693157854159392       ; tada!
The inverse, so to speak, of this is src-fit-envelope:
(define (src-fit-envelope e target-dur)
  (scale-envelope e (/ (src-duration e) target-dur)))
start-playing (:optional chans srate background)
If a play-list is waiting, this starts it. 'chans' defaults to 1, 'srate' defaults to 44100, 'background' defaults to #t. See play.scm or marks.scm.
start-progress-report (:optional snd chn)
This starts a progress-report.
stop-player (player)
This removes 'player' from the current play-list (see make-player).
stop-playing (:optional snd)
If 'snd' is playing, this stops it. If no argument is given, it stops all playback. See play.scm, popup.scm, stop-playing-hook, or stop-playing-selection-hook.
swap-channels (:optional snd1 chn1 snd2 chn2 beg dur edpos0 edpos1)
This swaps the indicated channels, between 'beg' and 'beg' + 'dur'. In simple cases, this is a virtual operation. swap-channels can be used to change channel order arbitrarily. For example, the following function reverses the channel order:
(define* (reverse-channels :optional snd)
  (let* ((ind (or snd (selected-sound) (car (sounds))))
	 (chns (chans ind)))
    (let ((swaps (inexact->exact (floor (/ chns 2)))))
      (as-one-edit
       (lambda ()
	 (do ((i 0 (+ 1 i))
	      (j (- chns 1) (- j 1)))
	     ((= i swaps))
	   (swap-channels ind i ind j)))))))
Channel rotation is similar, though slightly more work; see scramble-channels in examp.scm. Since swap-channels is a virtual operation in many cases, it's worth using it even where just a channel copy is desired; mono->stereo in extensions.scm for an example. Another example is swap-selection-channels in examp.scm.
sync (:optional snd)
sync returns the sound's 'sync' value (an integer, 0 = not sync'd). Several functions (scale-by, for example), apply to the currently selected sound and also to any other sounds that share its sync value. (I later decided that this was a bad idea, hence the regularized replacements). Sounds that share a given sync value move together when you drag an x-axis slider and so on.

The built-in Guile/Posix function named sync ("flush OS disk buffers") is available (are you sure you want this?) as %sync.
sync-max ()
This is the maximum sync setting seen so far — it provides a painless way to get a sync value that is guaranteed to be unique. To sync all currently open sounds:
    (let ((new-sync (+ 1 (sync-max))))
      (for-each (lambda (snd) (set! (sync snd) new-sync)) (sounds)))
time-graph? (:optional snd chn)
This is #t if the time domain graph is being displayed (the 'w' button).
time-graph-style (:optional snd chn)
This determines how time-domain data is displayed. The choices are:
    graph-lines  graph-dots  graph-filled  graph-lollipops  graph-dots-and-lines

    (set! (time-graph-style 0 4) graph-lollipops)
graph styles
time-graph-type (:optional snd chn)
If time-graph-type is graph-as-wavogram, the time domain waveform is displayed as a 'wavogram'. The default is graph-once. See also wavo-hop and wavo-trace.
tracking-cursor-style (:optional snd chn)
This is the cursor-style in effect when the cursor is tracking playback (with-tracking-cursor). tracking-cursor-style can be cursor-cross or cursor-line. If you want some other shape, use the function choice for cursor-style (that function's third argument can tell you when you're tracking).
transform-frames (:optional snd chn)
This returns either 0 if there is no transform, transform-size if transform-graph-type is graph-once, or (list spectrum-end time-slices fft-bins) if either a sonogram or a spectrogram is being displayed.
    :(set! (transform-graph?) #t) ; turn on fft display
    #t
    :(transform-frames)
    512
    :(set! (transform-graph-type) graph-as-sonogram)
    1