# Snd Customization and Extension

related documentation snd.html grfsnd.html clm.html sndlib.html sndscm.html index.html

- Introduction
- Snd Programming
  - Customizing Snd's appearance
    - Colors
    - Fonts
    - Graphics
    - Widgets
  - Customizing Snd's behavior
    - Global variables
    - Hooks
  - Snd's objects
    - Vcts
    - Sample-readers
    - Sound-data and Sndlib
    - Marks
    - Mixes
    - Regions and Selections
    - Sounds and channels
      - the control panel
    - Edit Lists
    - Transforms
    - Dialogs and Menus
    - Errors
    - Constants
    - Miscellaneous functions
  - Customization Part 2
- Index

## Introduction

Snd is a highly customizable, extensible program. The syntax used throughout this documentation is Scheme (a form of lisp) as implemented by the Gnu Guile library. You can also use Ruby, but need to make the obvious changes. I've tried to bring out to lisp 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 file of scheme code or a saved state file, specified via inter-process communication or from stdin from any other program (CLM and Emacs in particular), imbedded in a keyboard macro, or accessed in the lisp listener. 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, and (my typing is in this font and Snd's responses are in this font):

    >(+ 1 2)
    3

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.

## 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 and snd-test.scm. In addition, various examples of and extensions to Snd can be found in:

autosave.scm | auto-save (edit backup) support
bell.scm | the fm-bell from CLM
bird.scm | various North-American birds (from bird.clm)
draw.scm | graphics additions
dsp.scm | various DSP-related procedures
edit-menu.scm | Edit menu additions
effects.scm | an Effects menu based on examp.scm
env.scm | various envelope functions from CLM
enved.scm | envelope editor in lisp graph section
extensions.scm | extensions of Snd
fmv.scm | the fm-violin tied to real-time gtk graphics
glfft.scm | OpenGL for spectra (needs work)
goopsnd.scm | first look at Goops (Guile Object System) for Snd
hooks.scm | functions related to hooks
index.scm | snd-help locators
loop.scm | some Common Lisp syntax in Scheme
marks.scm | functions related to marks
mix.scm | functions related to mixes and tracks
moog.scm | Moog filter (from CLM)
musglyphs.scm | Music notation symbols (from CMN)
nb.scm | Popup File info etc
peak-env.scm | peak envelope support
popup.scm | Popup menu specializations
pqwvox.scm | phase-quadrature waveshaping (from CLM)
prc95.scm | Perry Cook's physical model examples (from CLM)
pvoc.scm | phase-vocoder examples
rgb.scm | color definitions
rtio.scm | real-time stuff
rubber.scm | rubber-sound
snd-motif.scm | Motif module (xm.c)
snd4.scm | backwards compatibility stuff
v.scm | fm-violin (from CLM)
ws.scm | with-sound implementation
xm-enved.scm | xm-based envelope editor
zip.scm | the zipper (a cross-fader, sort of) (from CLM)

### Customizing Snd's appearance

Snd's overall appearance is controlled first by the startup switches that choose the outermost widget; normally this is a paned window with a sound in each pane; -separate puts each sound in a separate window, and -notebook puts each sound on a separate page of a notebook widget. Similarly -horizontal and -vertical determine which way the outer panes are layed out. As panes (sounds) come and go, Snd's overall size may change (this is partly determined by the window manager, but is to some extent also up to Snd); many people find this distracting -- they would rather the overall window size try to stay the same. The Snd variable associated with this is "auto-resize"; it can be accessed as follows (we're typing to the listener here, as described above):

    >(auto-resize)
    #t
    >(set! (auto-resize) #f)
    #f

As this illustrates, variables in Snd are accessed as though each were a function, and set using set!. auto-resize's current value is accessed above via `(auto-resize)`, and set to a new value via `(set! (auto-resize) #f)`. #t is Scheme for "true" (often 1 in C, t in Lisp), #f is "false" (0 in C, nil in Lisp). A statement like `(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.

In the sections below, the variable or function name is followed by either its default value or the function arguments, then some brief description of what it does. So,

      basic-color           ivory2          main Snd color.

means there's a thing called basic-color (and a way to set it via "(set! (basic-color) ...)"), that its default value is ivory2 (see rgb.scm for a definition of this color), and that it is the main Snd color.

#### Colors

A color in Snd is an object with three fields representing the rgb (red green blue) settings as numbers between 0.0 and 1.0. A color object is created via make-color:

    >(define blue (make-color 0 0 1))

This declares the Scheme variable "blue" and gives it the value of the color whose rgb components include only blue in full force. The X11 color names are defined in rgb.scm. Now, in Snd, the basic color is known as "basic-color" (another Scheme variable); we can set it:

    >(set! (basic-color) blue)

The color variables are:

`basic-color` | `ivory2` | main Snd color.
`cursor-color` | `red` | cursor color.
`data-color` | `black` | color of data in unselected graph.
`enved-waveform-color` | `blue` | color of waveform displayed in envelope editor.
`filter-waveform-color` | `blue` | color of control panel filter waveform.
`graph-color` | `white` | background color of unselected graph.
`highlight-color` | `ivory1` | highlighting color.
`listener-color` | `aliceblue` | background color of lisp listener.
`listener-text-color` | `black` | text color in lisp listener.
`mark-color` | `red` | color of mark indicator.
`mix-color` | `darkgray` | color of mix waveforms.
`selected-mix-color` | `lightgreen` | color of selected mix waveform.
`position-color` | `ivory3` | position slider color
`pushed-button-color` | `lightsteelblue1` | color of pushed button.
`sash-color` | `lightgreen` | color of paned window sashes.
`selected-data-color` | `black` | color of data in currently selected graph.
`selected-graph-color` | `white` | background color of currently selected graph.
`selection-color` | `lightsteelblue1` | color of selected portion of graph.
`text-focus-color` | `white` | color of text field when it has focus.
`zoom-color` | `ivory4` | zoom slider color.

    (define beige (make-color 0.96 0.96 0.86))
    (define blue (make-color 0 0 1))
    (set! (selected-graph-color) beige)
    (set! (selected-data-color) blue)

In addition, the various transforms can be displayed using colormaps. The following variables and functions control this:

`color-cutoff` | 0.003 | In spectra, sets the lowest data value that will be colored.
`color-dialog` | () | Create the Color dialog (to choose a colormap etc), if necessary, activate it and return the dialog widget.
`color-inverted` | #t | The 'invert' button in the color dialog, negated (hunh?!).
`color-scale` | 0.5 | The darkness setting in the color dialog, divided by 100.
`colormap` | 0 | Colormap choice for various displays (see the Color Editor). This should be an integer between -1 and 15. The maps (from 0 to 15) are: gray, hsv, hot, cool, bone, copper, pink, jet, prism, autumn, winter, spring, summer, colorcube, flag, and lines. -1 means black and white.

The color object handlers are:

`color? (obj)` | #t if obj is a color object (see make-color). |
`color->list (obj)` | return list (r g b) of color components. |
`make-color (r g b)` | return a color object using the red/green/blue values. If the object is called as a function, it returns the list of rgb values. If, for example, blue is a defined color, `(blue)` is the same as `(color->list blue)`. Two colors are equal (i.e. equal? returns #t) if their rgb values are the same. |

(British spelling enthusiasts can use "colour" throughout Snd by building it with the CFLAG -DSTR_OR=\"our\").

#### Fonts

Fonts in Snd are strings containing a description of the desired font. These can be the abbreviated forms such as "8x14" or a full X font name such as "-misc-fixed-bold-r-normal--*-140-*-*-c-*-iso8859-1". The font variables are:

`axis-label-font` | used in axis labels
`axis-numbers-font` | used in axis tick numbers
`bold-button-font` | used by various buttons and labels
`button-font` | used by various buttons and labels
`help-text-font` | help dialog text font
`listener-font` | listener font
`tiny-font` | smallest font used

    (set! (listener-font) "9x15")
    (set! (help-text-font) "9x15")
    (set! (button-font) "-adobe-times-medium-r-*-*-14-*-*-*-*-*-*-*")
    (set! (bold-button-font) "-adobe-times-bold-r-*-*-14-*-*-*-*-*-*-*")
    (set! (axis-label-font) "-adobe-times-medium-r-normal-*-18-*-*-*-*-*-*-*")
    (set! (axis-numbers-font) "9x15")

See also load-font and current-font below.

#### Widgets

This section is new, and in flux, but here's what's currently implemented:

`widget-position (widget)` | return a list giving the widget x and y positions
`widget-size (widget)` | return a list giving the widget width and height. The corresponding set! forms also take a list: `(set! (widget-position (cadr (main-widgets))) (list 300 100))`
`widget-text (widget)` | return or set the contents of text widget
`recolor-widget (widget color)`
`show-widget (widget)`
`hide-widget (widget)`
`main-menu (menu)` |
`main-widgets ()` |
`menu-widgets ()` |
`sound-widgets (snd)` |
`channel-widgets (snd chn)` |
`dialog-widgets ()` |

The four "-widgets" procedure return lists of possibly useful widgets:

      main-widgets:    '(0:top-level-application 1:top-level-shell 2:main-pane 3:main-sound-pane 4:listener-pane 5:notebook-outer-pane)
      menu-widgets:    '(0:top-level-menu-bar 1:file-menu 2:edit-menu 3:view-menu 4:options-menu 5:help-menu)
      sound-widgets:   '(0:main-pane 1:name-label 2:control-panel 3:minibuffer 4:play 5:filter-graph 6:unite 7:minibuffer-label 8:name-icon)
      channel-widgets: '(0:graph 1:w 2:f 3:sx 4:sy 5:zx 6:zy 7:edhist)
      dialog-widgets:  '(0:color_dialog 1:orientation_dialog 2:enved_dialog 3:error_dialog
                         4:yes_or_no_dialog 5:transform_dialog 6:file_open_dialog
                         7:file_save_as_dialog 8:view_files_dialog 9:raw_data_dialog
                         10:new_file_dialog 11:file_mix_dialog 12:edit_header_dialog
                         13:find_dialog 14:help_dialog 15:completion_dialog 16:mix_panel_dialog
                         17:print_dialog 18:recorder_dialog 19:region_dialog 20:stats_dialog)

I've only included the ones I needed immediately (for autotesting), but all Snd widgets are potentially (and easily) accessible; if you need access to one that isn't included, let me know. dialog-widgets entries will be #f for any dialogs that have not yet been created.

      (recolor-widget (cadr (sound-widgets)) (make-color 1 0 0))

makes the sound name label red. To remove the y-position slider (which is only there for looks):

      (hide-widget (list-ref (channel-widgets) 4))

See also examp.scm.

#### Graphics

It is possible to draw directly on any of the channel graphs. Simple examples include the show-original after-graph-hook function, and the x-cursor function that draws an "x" shaped cursor. The lowest level procedures are:

`axis-info (snd chn grf)`
| returns a list describing the specified axis: `'(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)`. This might 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. The procedure x->position could be defined as:

    (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)))))))

`copy-context`
| the graphics mode to use to draw over whatever is currently in a graph. The "contexts" refer to graphics contexts used throughout Snd; the copy-context copies into the current graph, whereas the cursor-context uses XOR. The error returned for an unimplemented 'context' is 'no-such-graphics-context.
`current-font (snd chn context)`
| returns the current font (also settable).
`cursor-context`
| the graphics mode for XOR drawing in the cursor color (for cursors, normally).
`draw-axes (wid gc label x0 x1 y0 y1 style axes)`

(Motif only currently) draws axes in the widget wid, using the graphics context gc, with the x-axis label label going from x0 to x1 (floats) along the x axis, y0 to y1 along the y axis, with x-axis-style style (x-axis-in-seconds etc); whether the axes are actually displayed or just implied depends on axes. Returns a list of the actual (pixel) axis bounds. See, for example, the scanned-synthesis display code in snd-motif.scm.

`draw-dot (x0 y0 dot-size snd chn context)`
| draws a dot at (x0 y0) of dot-size pixels diameter in the given graph
`draw-dots (positions dot-size snd chn context)`
| draws dots of size dot-size from the (x y) pairs in the vector positions in the given context.
`draw-line (x0 y0 x1 y1 snd chn context)`
| draws a line from (x0 y0) to (x1 y1) in the given context.
`draw-lines (lines size snd chn context)`
| draws lines following the (x y) pairs in the vector lines in the given context. make-bezier-1 in musglyphs.scm can be used to draw Bezier curves.
`draw-string (text x0 y0 snd chn context)`
| draws a string (text) in the current font and foreground color starting at (x0 y0) in the given context. The next procedure draws a box over sample 1000 (if it is visible) with the text "hiho":

    (define show-hiho
      ;; show "hiho" in a box over sample 1000
      (lambda (snd chn)
        (define (draw-rectangle x0 y0 width height)
          (draw-line x0 y0 (+ x0 width) y0)
          (draw-line (+ x0 width) y0 (+ x0 width) (+ y0 height))
          (draw-line (+ x0 width) (+ y0 height) x0 (+ y0 height))
          (draw-line x0 (+ y0 height) x0 y0))
        (let ((ls (left-sample snd chn))
              (rs (right-sample snd chn)))
          (if (and (< ls 1000)
                   (> rs 1000))
    	  (let ((pos (x->position (/ 1000.0 (srate)))))
                (draw-rectangle pos 10 50 20)
                (draw-string "hiho" (+ pos 5) 24))))))

    (add-hook! after-graph-hook show-hiho)

`fill-rectangle (x0 y0 width height snd chn context)`
| draws a filled rectangle (in the current foreground color) from (x0 y0) of size (width height).
`fill-polygon (points snd chn context)`
| draws a filled polygon whose vertices are in points.

    (define (-> x0 y0 size snd chn)
      "draw an arrow pointing (from the left) at the point (x0 y0)"
      (let ((points (make-vector 8)))
        (define (point i x y)
          (vector-set! points (* i 2) x)
          (vector-set! points (+ (* i 2) 1) y))
        (define (arrow-head x y)
          (point 0 x y)
          (point 1 (- x (* 2 size)) (- y size))
          (point 2 (- x (* 2 size)) (+ y size))
          (point 3 x y)
          (fill-polygon points snd chn))
        (arrow-head x0 y0)
        (fill-rectangle (- x0 (* 4 size))
    		    (inexact->exact (- y0 (* .4 size)))
    		    (* 2 size)
    		    (inexact->exact (* .8 size))
                        snd chn)))

| musglyphs.scm has some elaborate examples that use fill-polygon to draw various music symbols.
`foreground-color (snd chn context)`

returns the current foreground color (also settable). The following gives us a green cursor:

      (set! (foreground-color 0 0 cursor-context) (make-color 1 0 1))

We can goof around with colors and fonts:

    (define new-font (load-font "-adobe-helvetica-bold-r-*-*-14-*-*-*-*-*-*-*"))

    (define show-hiho
      ;; show a red "hiho" in the helvetica bold font on a gray background
      (lambda (snd chn)
        (let ((ls (left-sample snd chn))
              (rs (right-sample snd chn)))
          (if (and (< ls 1000)
                   (> rs 1000))
    	  (let ((pos (x->position (/ 1000.0 (srate))))
    		(old-color (foreground-color)))
    	    (set! (foreground-color) (make-color .75 .75 .75))
                (fill-rectangle pos 10 50 20)
    	    (set! (foreground-color) (make-color 1 0 0))
    	    (if new-font (set! (current-font) new-font))
                (draw-string "hiho" (+ pos 5) 24)
    	    (set! (foreground-color) old-color))))))

`graph-data (data snd chn context low high graphics-style)`

graph-data displays data in the time domain graph of snd's channel chn using the graphics context context (normally copy-context), placing the data in the recipient's graph between points low and high in the drawing mode (graphics-style). With this function and make-graph-data we can overlay sounds, overlay different versions of the same sound, place a portion of a sound over another at an arbitrary point, and so on (see draw.scm):

    (define (display-samps-in-red snd chn)
      "display samples 1000 to 2000 in red whenever they're in the current view"
      (let ((left (left-sample snd chn))
    	(right (right-sample snd chn))
    	(old-color (foreground-color snd chn))
    	(red (make-color 1 0 0)))
        (if (and (< left 2000)
    	     (> right 1000))
    	(let* ((data (make-graph-data snd chn)))
    	  (if (vct? data)                      ;the simple, one-sided graph case
    	      (let* ((samps (- (min right 2000)
    			       (max left 1000)))
    		     (offset (max 0 (- 1000 left)))
    		     (new-data (vct-subseq data offset (+ offset samps))))
    		(set! (foreground-color snd chn) red)
    		(graph-data new-data snd chn copy-context (max 1000 left) (min 2000 right))
    		(set! (foreground-color snd chn) old-color))
    	      (let* ((low-data (car data))     ;the two-sided envelope graph case
    		     (high-data (cadr data))
                         ;; we need to place the red portion correctly in the current graph
                         ;; so the following is getting the "bin" numbers associated with
                         ;; samples 1000 and 2000
    		     (size (vct-length low-data))
    		     (samps (- right left))
    		     (left-offset (max 0 (- 1000 left)))
    		     (left-bin (inexact->exact (/ (* size left-offset) samps)))
    		     (right-offset (- (min 2000 right) left))
    		     (right-bin (inexact->exact (/ (* size right-offset) samps)))
    		     (new-low-data (vct-subseq low-data left-bin right-bin))
    		     (new-high-data (vct-subseq high-data left-bin right-bin)))
    		(set! (foreground-color snd chn) red)
    		(graph-data
                      (list new-low-data new-high-data) snd chn copy-context left-bin right-bin)
    		(set! (foreground-color snd chn) old-color)))))))

    (add-hook! after-graph-hook display-samps-in-red)

`load-font (font-name)`

loads the font font-name (an X-style font spec), and returns a handle for it (for current-font below).

      (define new-font (load-font "-adobe-helvetica-bold-r-*-*-14-*-*-*-*-*-*-*"))

`make-graph-data (snd chn edit-position low-sample high-sample)`

Use make-grph-data to get the currently displayed data (i.e. the actual waveform displayed in the graph, which can be based on an overall envelope rather than the actual samples). It returns either a vct (if the graph has one trace), or a list of two vcts (the two sides of the envelope graph). edit-position defaults to the current edit history position, low-sample defaults to the current window left sample, and high-sample defaults to the current rightmost sample. The result can be use in the "lisp graph":

    (define display-db
      (lambda (snd chn)
        "(display-db snd chn) is a lisp-graph-hook function to display the time domain data in dB"
        (let* ((datal (make-graph-data snd chn))
    	   (data (if (vct? datal) datal (cadr datal)))
               (sr (srate snd)))
          (define (dB val)
    	(if (< val .001)
    	    -60.0
    	    (* 20.0 (log10 val))))
          (vct-do! data (lambda (i)
    		      (vct-set! data i (+ 60.0 (dB (abs (vct-ref data i)))))))
          (graph data "dB"
    	     (/ (left-sample snd chn) sr) (/ (right-sample snd chn) sr)
    	     0.0 60.0
    	     snd chn))))

    (add-hook! lisp-graph-hook display-db)

| Here we are taking whatever is displayed in the time domain, and presenting the same thing in dB in the lisp graph. display-energy in examp.scm is another example. But the real power of this function comes from its use with graph-data. The latter takes its argument (either a vct or a list of two vcts), and displays it in any channel's time domain graph using its current graph-style.
`mark-context`
| the graphics context to use to draw a mark (XOR mode).
`selection-context`
| the graphics context for XOR drawing in the selection color.

This only scratches the surface of these functions; I'll eventually collect more examples into draw.scm.

### 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; variables and hooks. A hook is a list of "callbacks" invoked whenever the associated event happens. When Snd exits, for example, any functions found on the exit-hook list are evaluated; if any of them returns #t, Snd does not exit.

    (define unsaved-edits?
      (lambda (ind)
        (and (< ind (max-sounds))
    	 (or (and (sound? ind)
    		  (> (car (edits ind)) 0)
    		  (report-in-minibuffer "there are unsaved edits")
    		  #t)
    	     (unsaved-edits? (+ ind 1))))))

    (add-hook! exit-hook (lambda () (unsaved-edits? 0)))

Now when Snd is told to exit, it checks 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. Slightly special are the hooks output-comment-hook and output-name-hook; these refer to the default file name and comment displayed when you save a sound via the "save" dialog.

#### Global variables

The main variables affecting Snd's overall behavior are:

`ask-before-overwrite` | `#f` | (Save-as): ask before overwriting an existing file
`audio-input-device` | `mus-audio-default`
| Audio input device (for the recorder)
`audio-output-device` | `mus_audio-default`
| Audio output device (for the play button)
`audio-state-file` | `".snd-mixer"`
| Name of file in which current audio hardware settings are saved (only works in OSS and SGI).
`auto-resize` | `#t` | should Snd window resize upon open/close (see AutoResize)
`auto-update` | `#f` | should Snd update a file automatically if it changes on disk due to some other process.
`auto-update-interval` | `60` | Time (seconds) between background checks for changed file on disk (see auto-update). If less than 0.0, the auto-update background process is turned off.
`dac-combines-channels` | `#t` | channels mixed into available channels upon dac output if necessary.
`dac-size` | `256` | Audio output buffer size (not always meaningful).
`data-clipped` | `#f` | If #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 causes wrap-around which makes the out-of-range values very obvious.
`default-output-chans` | `1` | The default number of channels when a new or temporary file is created, or a save dialog is opened.
`default-output-format` | `mus-bshort` | The default data format when a new or temporary file is created, or a save dialog is opened. (mus-bshort is from sndlib, standing for 16-bit big-endian data). Use mus-out-format for fastest IO.
`default-output-srate` | `22050` | The default srate when a new or temporary file is created, or a save dialog is opened.
`default-output-type` | `mus-next` | The default header type when a new or temporary file is created, or a save dialog is opened. (mus-next is from sndlib, standing for the NeXT/Sun sound file header).
`emacs-style-save-as` | `#f` | File:Save-as dialog option remains with current file (#f) or goes to new file (#t).
`eps-bottom-margin` | `0.0` | bottom margin used in snd.eps (Print command) (PS units). PostScript units are 1/72 of an inch (a "point" in printer jargon); an inch is 2.54 cm:

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

    (define (cm-to-ps cm)
      (* cm (/ 72.0 2.54)))

|  | 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"`
| Name of the Postscript file produced by the File Print option. See also the epsFile resource.
`eps-left-margin` | `0.0` | left margin used in snd.eps (Print command) (PS units).
`eps-size` | `1.0` | scaler used in snd.eps (Print command) for overall picture size.
`filter-env-in-hz` | `#f` | If #t, filter env x axis is in Hz, otherwise 0 to 1.0.
`graph-cursor` | `XC_crosshair (34)`
| cursor displayed following mouse in graph The X cursors are declared in /usr/X11R6/include/X11/cursorfont.h or some such file.
`hankel-jn` | 0 | Bessel function to use in Hankel Transform.
`ladspa-dir` | `#f` | Name of directory for LADSPA plugin libraries (can override or replace LADSPA_PATH).
`max-regions` | 16 | Size of region stack.
`minibuffer-history-length` | `8` | Length of minibuffer M-p/M-n history. If set, sounds that are currently open are not affected, only subsequently opened sounds.
`movies` | `#t` | If #t, the mix graphs are updated constantly as the mouse drags the mix tag.

`save-dir`

`#f`

Name of directory for saved-state files.
If #f, all saved-state is placed (as text) in the saved-state.scm file. If you've edited a file, this can be unwieldy. By setting save-dir, Snd instead places the necessary intermediate files in save-dir, with the file names in the saved-state file. The assumption here is that you won't mess with the save-dir files until you no longer want to restart that version of Snd.
`(set! (save-dir) "/tmp")`.

`save-state-file` | `"saved-snd.scm"`
| The default saved state file name.
`selection-creates-region` | `#t` | If #t, a region is created whenever a selection is made.
`show-backtrace` | `#f` | If #t, show backtrace automatically upon error.
`show-indices` | `#f` | If #t, each sound's name is preceded by its index (in the sound pane).
`show-selection-transform` | `#f` | If #t, display the transform of the current active selection, if any.
`show-usage-stats` | `#f` | If #t, show approximate memory and disk space usage of current edit trees.
`sinc-width` | `10` | 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.
`temp-dir` | `#f` | Name of directory for temporary files. #f usually means "/tmp" or "/var/tmp".
`trap-segfault` | `#t` | If #t, try to catch segfaults and continue anyway.

`use-sinc-interp`

`#t`

If #t, use high quality src, rather linear interpolation.
This can significantly increase the computational burden on the computer; if you're trying to play 8 channel 44Khz sounds on an older machine, It may help to set use-sinc-interp to #f. In fact, if you're running a machine in the 100 MHz range, even stereo 44.1kHz can be a problem -- if the sound has clicks or stutters or seems horribly distorted when the "speed" control is not at 1.0, try setting this variable to #f.

`window-height`

`0`

The current Snd window height in pixels. This is the same as
`(cadr (widget-size (cadr (main-widgets))))`
except at startup when the window-height function and friends defer the set 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` | The current Snd window width in pixels.
`window-x` | `-1` | The current Snd window left side in pixels. This is (usually) the same as `(car (widget-position (cadr (main-widgets))))`
`window-y` | `-1` | The current Snd window upper side in pixels (X numbering starts at 0 at the top).
`with-background-processes` | `#t` | Determines whether Snd should use background (idle time) processes for ffts and so forth. (Intended primarily for auto-testing).
`zoom-focus-style` | `zoom-focus-active`
| This determines what a zoom action focuses (centers) on. See Zoom options.

#### Hooks

When some user-interface action takes place, some code is called that responds to that action; these functions are sometimes called callbacks; in Guile 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 Guile 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" in the listener by:

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

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 #t, the hook immediately returns #t (ignoring the remaining functions), whereas in the other cases, the result returned by the hook is the result of the last function in the list. In the list below the arguments after the hook name refer to the functions invoked by the hook.

In Ruby, the hook is a global variable that holds either a procedure or is false (#f).

`after-apply-hook (snd)` | progn
| called when 'Apply' finishes.
`after-graph-hook (snd chn)` | progn
| called after a graph is updated or redisplayed (see, for example, display-samps-in-red).
`after-open-hook (snd)` | progn
| called just before the new file'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, and filled graphs:

    (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)))

`before-apply-hook (snd)` | or
| called when 'Apply' is clicked or apply-controls called. If it returns #t, the apply is aborted.
`before-transform-hook (snd chn)` | progn
| called just before an FFT (or spectrum) is calculated. If it returns an integer, it is used as the starting point of the fft. 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)))

`close-hook (snd)` | or
| called each time a file is closed (before the close). If it returns #t, the file is not closed.

    (add-hook! close-hook
      (lambda (snd)
        (system \"sndplay wood16.wav\")))

`dac-hook (data)` | progn
| called just before data is sent to DAC passing data as sound-data object.
`draw-mark-hook (id)` | progn
| called before a mark is drawn (in XOR mode). If the hook returns #t, the mark is not drawn.
`drop-hook (filename)` | or
| called each time Snd receives a drag-and-drop event, passing the hook the filename. If it returns #t, the file is not opened. (Drag-and-drop only sort-of works in Snd -- try dropping the file icon on the menu bar).
`during-open-hook (fd name reason)` | progn
| 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)
            (mus-file-set-prescaler fd 500.0))))

| The prescaling affects only sound data made up of floats or doubles.
`enved-hook (env pt new-x new-y reason)` | composition

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 is reason which 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 (max-x env)))
                (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 might call this a "function composition" method combination.
`exit-hook ()` | or
| called upon exit. If it returns #t, Snd does not exit. This can be used to check for unsaved edits, or to perform cleanup activities (see, for example, unsaved-edits?).
`graph-hook (snd chn y0 y1)` | or
| called each time a graph is updated or redisplayed. If it returns #t, the display is not updated.

    (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)))

`initial-graph-hook (snd chn dur)` | or

called the first time a given channel is displayed. If it returns a list, the list's contents are interpreted as:
`'(x0 x1 y0 y1 label ymin ymax)`
(all values optional), where these numbers set the initial axis limits and settings. This replaces the variable initial-x0 and its friends. The default (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 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 the data limits (rather than the default -1.0 to 1.0 as above), you can use mus-sound-maxamp, but that can in some cases require a long process of reading the underlying file. The following hook procedure uses the max amp data only if it is already available:

    (add-hook! initial-graph-hook
      (lambda (snd chn dur)
        (if (mus-sound-maxamp-exists? (file-name snd))
    	(let* ((amp-vals (mus-sound-maxamp (file-name snd)))
    	       (max-val (list-ref amp-vals (+ (* chn 2) 1))))
    	  (list 0.0 dur (- max-val) max-val))
    	(list 0.0 dur -1.0 1.0))))

A similar problem affects the dur argument. If the file is very long, Snd starts a background process reading its data to get an overall amplitude envelope of the file, 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 notice if the duration is very long, and if so, set up an after-graph-hook function that looks at the channel's peak-env-info, waiting for it to be completed before finally asking that the entire file be displayed:

    (use-modules (ice-9 common-list))
    (define startups '())

    (define (remove-startup snd chn)
      (set! startups (remove-if (lambda (n) (and (= (car n) snd) (= (cadr n) chn))) startups)))

    (define (add-startup snd chn)
      (set! startups (cons (list snd chn) startups)))

    (define (starting? snd chn)
      (find-if (lambda (n) (and (= (car n) snd) (= (cadr n) chn))) startups))

    (define (wait-for-completed-envelope snd chn)
      (if (starting? snd chn)
          (let ((e (peak-env-info snd chn)))
    	(if (and e (car e))
    	    (begin
    	      (set! (x-bounds snd chn) (list 0.0 (/ (frames snd chn) (srate snd))))
    	      (remove-startup snd chn))))))

    (add-hook! initial-graph-hook
      (lambda (snd chn dur)
        (if (< dur 100.0)
    	(if (mus-sound-maxamp-exists? (file-name snd))
    	    (let* ((amp-vals (mus-sound-maxamp (file-name snd)))
    		   (max-val (list-ref amp-vals (+ (* chn 2) 1))))
    	      (list 0.0 dur (- max-val) max-val))
    	    (list 0.0 dur -1.0 1.0))
    	(begin
    	  (add-startup snd chn)
    	  #f))))

    (add-hook! after-graph-hook wait-for-completed-envelope)

| But we needed three hours of sound by yesterday or the rent doesn't get paid -- no time to wait on a background process! So, we read and write the entire peak-env-info contents ourselves (see peak-env.scm):

    (use-modules (ice-9 format))

    (define save-peak-env-info #t)

    (define (peak-env-info-file-name snd chn)
      (format #f "~A-peaks-~D" (file-name snd) chn))
      ;; or, if we want these files saved in a directory named "peaks":
      ;;   (format #f "~~/peaks/~A-peaks-~D" (short-file-name snd) chn))

    (define (save-peak-env-info-at-close snd)
      ;; intended as a close-hook function
      (if (and save-peak-env-info
    	   (not (null? (peak-env-info snd 0 0))))
          (do ((i 0 (1+ i)))
    	  ((= i (chans snd)))
    	(let ((peak-file (mus-expand-filename (peak-env-info-file-name snd i))))
    	  (if (or (not (file-exists? peak-file))
    		  (< (file-write-date peak-file)
    		     (file-write-date (file-name snd))))
    	      (write-peak-env-info-file snd i peak-file)))))
      #f)

    (define (restore-peak-env-info-upon-open snd chn dur)
      ;; intended as an initial-graph-hook-function
      (let ((peak-file (mus-expand-filename (peak-env-info-file-name snd chn))))
        (if (and (file-exists? peak-file)
    	     (> (file-write-date peak-file)
    		(file-write-date (file-name snd))))
    	(read-peak-env-info-file snd chn peak-file))
        #f))

    (add-hook! close-hook save-peak-env-info-at-close)
    (add-hook! exit-hook (lambda () (for-each save-peak-env-info-at-close (sounds)) #f))
    (add-hook! initial-graph-hook restore-peak-env-info-upon-open)

| This writes the peak-env data when a file is closed, and reads it upon opening the file, if it exists; it provides essentially immediate display of any size file.
`just-sounds-hook (filename)` | or
| called on each file (after the sound file extension check) if the just-sounds button is set. Return #f to filter out filename.

    (add-hook! just-sounds-hook
      (lambda (name) ;display only stereo files in file selection dialog
        (and (not (= (mus-sound-header-type name) mus-raw))
             ;;any unrecognized file is considered "raw"
             (= (mus-sound-chans name) 2))))

| This currently only works in Motif; the Gtk file selection dialogs don't provide a way to specialize the directory read.
`key-press-hook (snd chn key state)` | or
| called upon key press while mouse is in lisp graph. If it returns #t, the key press is not passed to the main handler. state refers to the control, meta, and shift keys.
`lisp-graph-hook (snd chn)` | progn
| called just before the lisp graph is updated or redisplayed (see, for example, display-db).
`mark-hook (id snd chn reason)` | progn

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 active, 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))))))))

`mark-click-hook (id)` | progn
| called when a mark is clicked, return #t to squelch message.

    (add-hook! mark-click-hook
      (lambda (n)
        (help-dialog "Mark Help" (number->string (mark-sample n)))
        #t))

`mark-drag-hook (id)` | progn
| called when mark id 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 "~D (~,3F) ~A[~D]"
                  samp
                  (/ samp (srate (car sndchn)))
                  (short-file-name (car sndchn))
                  (cadr sndchn)))))

    (add-hook! mark-drag-hook report-mark-location)

`mix-amp-changed-hook (id)` | progn
| called when mix amp changes via mouse (id = mix id). If #t, the actual remix is the hook's responsibility.
`mix-position-changed-hook (id samps)` | progn
| called when mix moves via mouse (id = mix id, samps = samps moved). If #t, the actual remix is the hook's responsibility (see, for example, reposition-track in mix.scm).
`mix-speed-changed-hook (id)` | progn
| called when mix speed changes via mouse (id = mix id). If #t, the actual remix is the hook's responsibility (see, for example, respeed-track in mix.scm).
`mouse-click-hook (snd chn button state x y axis)` | or
| called upon mouse button click (press and release without drag). If it returns #t, the click is ignored by Snd. See the current-window-location procedures 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)

`mouse-drag-hook (snd chn button state x y)` | progn
| called upon mouse motion (with button pressed) within lisp graph (see, for example, enved.scm).
`mouse-enter-graph-hook (snd chn)` | progn
| 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 name)` | progn
| called when a file viewer or region label is entered by the mouse. The 'type' is 0 for the current files list, 1 for previous files, and 2 for regions. The 'position' is the scrolled list position of the label. The label itself is 'label'. We could 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))
            (help-dialog name (finfo name)))))

| See also files-popup-buffer in examp.scm
`mouse-enter-listener-hook (widget)` | progn
| called when the mouse enters the lisp 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 could 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)` | progn
| 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)` | progn
| called when the mouse leaves a channel's drawing area (graph pane).
`mouse-leave-label-hook (type position name)` | progn
| called when the mouse exits one of the labels covered by mouse-enter-label-hook.
`mouse-leave-listener-hook (widget)` | progn
| called when the mouse leaves the lisp listener pane.
`mouse-leave-text-hook (widget)` | progn
| called when the mouse leaves a text widget.
`mouse-press-hook (snd chn button state x y)` | progn
| called upon mouse button press within lisp graph (see, for example, enved.scm).
`mouse-release-hook (snd chn button state x y)` | progn
| called upon mouse button release within lisp graph (see, for example, enved.scm).
`multichannel-mix-hook (ids)` | progn
| called when a multichannel mix happens in a sync'd sound. ids is a list of mix id numbers. See mix.scm for an example.
`mus-error-hook (error-type error-message)` | or
| called upon mus_error. If it returns #t, Snd ignores the error (it assumes you've handled it via the hook).
`name-click-hook (snd)` | or
| called when sound name clicked. If it returns #t, the usual informative minibuffer babbling is squelched.
`new-widget-hook (widget)` | progn
| called each time a dialog or a new set of channel or sound widgets is created.
`open-hook (filename)` | or
| called each time a file is opened (before the actual open). If it returns #t, the file is not opened (see the Buffer Menu code in examp.scm). If it returns a string, that file is opened instead of the original one.
`open-raw-sound-hook (filename current-choices)` | progn

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). The list is interpreted as '(chans srate data-format data-location data-length) where trailing elements can be omitted (location defaults to 0, and 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.

`output-comment-hook (str)` | concat
| called in Save-As dialog, passed current sound's comment, if any. If more than one hook function, results are concatenated. If the hook is empty, the current comment is used.

    (add-hook! output-comment-hook
      (lambda (str)
        (string-append "written "
          (strftime "%a %d-%b-%Y %H:%M %Z"
            (localtime (current-time))))))

`output-name-hook ()` | progn
| called in File New dialog, setting the file name in the associated text widget.

    (let ((file-ctr -1))
      (add-hook! output-name-hook
        (lambda ()
          (set! file-ctr (+ file-ctr 1))
          (format #f "~A-~D.snd"
    	(strftime "%b-%d" (localtime (current-time)))
            file-ctr))))

`play-hook (samps)` | progn
| called each time a buffer (size: samps) is about to be filled for the DAC (see enved.scm and marks.scm).
`previous-files-select-hook (filename)` | or
| called each time a file is selected in the View Files dialog's previous files list. If it returns #t, the file is not opened in Snd (the default action). We can use this hook to use the previous files list for whatever purpose seems handy. For example, say we want to click a file to have it mixed into the currently selected sound at the cursor:

    (add-hook! previous-files-select-hook
      (lambda (file)
        (mix-sound file (cursor))
        #t))

`print-hook (text)` | progn

called each time some Snd-generated response (text) is about to be appended to the listener. If it returns some non-#f result, Snd assumes you've sent the text out yourself, as well as any needed prompt. This is intended to make it possible to distinguish Snd responses from user-typing, or add arbitrarily fancy prompts, etc. Here are two examples, the first adds a timestamp, the second (in Gtk) displays Snd's responses in red:

    (add-hook! print-hook
      (lambda (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)))))

    (add-hook! print-hook
      (lambda (msg)
        (let ((old-color (listener-text-color)))
        (set! (listener-text-color) (make-color 1 0 0))
        (snd-print msg)
        (set! (listener-text-color) old-color))
        (snd-print (listener-prompt))))

`property-changed-hook (command)` | or
| called when Snd sees a SND_COMMAND window property change. If it returns #t, the command is not evaluated.
`read-hook (text)` | or
| called each time a line is typed into the listener (it is triggered by the carriage return). If it returns #t, Snd assumes you've dealt 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)
      (let ((res #f))
        (add-hook! read-hook (lambda (str) (set! res str) #t))
        (snd-print prompt)
        (do () ((or (c-g?) res)))
        (reset-hook! read-hook)
        res))

See also snd-debug in extensions.scm.
`save-hook (snd name)` | or
| called each time a file is about to be saved. If it returns #t, the file is not saved. name is #f unless the file is being saved under a new name (as in sound-save-as). (See the auto-save code in autosave.scm).
`select-channel-hook (snd chn)` | progn
| called when a channel is selected (after the sound has been selected). The arguments are the sound's index and the channel number.
`select-mix-hook (id)` | progn
| called when a mix is selected. The argument is the newly selected mix's id.
`select-sound-hook (snd)` | progn
| called when a sound is selected. The argument is the about-to-be-selected sound's index.
`snd-error-hook (error-message)` | or
| called upon snd_error. If it returns #t, Snd flushes the error (it assumes you've reported it via the hook).

    (add-hook! snd-error-hook
      (lambda (msg)
        (play "bong.snd")
        #f))

`snd-warning-hook (warning-message)` | or
| 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
| called upon start-up. If it returns #t, snd exits immediately (see, for example, no-startup-file?). Say we are so annoyed by the X/Motif file browser that we want Snd to exit back to the shell if its file argument is not found (this code obviously has to be in the init file):

    (define no-startup-file?
      (lambda (ind file)
        (if (= ind (max-sounds))
    	(begin
    	  (write (string-append "can't open " file) (current-error-port))
    	  (newline (current-error-port))
    	  #t)
    	(if (sound? ind)
    	    #f
    	    (no-startup-file? (+ ind 1) file)))))

    (add-hook! start-hook
      (lambda (file)
        (if (> (string-length file) 0)
            (no-startup-file? 0 file)
            #f)))

`start-playing-hook (snd)` | or
| called when a play request is triggered. If it returns #t, snd does not play (see, for example, report-mark-names in marks.scm).
`stop-dac-hook()` | progn
| called upon mus_audio_close (when DAC is turned off).
`stop-playing-channel-hook (snd chn)` | progn
| called when a sound finishes playing.
`stop-playing-hook (snd)` | progn
| called when a sound finishes playing. The following code plays a sound over and over until you type C-g:

    (define (play-until-c-g)
      (define (play-once snd)
        (if (c-g?)
            (remove-hook! stop-playing-hook play-once)
            (play 0 snd)))
      (add-hook! stop-playing-hook play-once)
      (play))

Here's somewhat brute-force code to play a sound a given number of times.

    (define (play-often n)
      (let ((plays (- n 1)))
        (define (play-once snd)
          (if (or (= plays 0)
    	      (c-g?))
    	  (remove-hook! stop-playing-hook play-once)
    	  (begin
    	    (set! plays (- plays 1))
    	    (play 0 snd))))
        (add-hook! stop-playing-hook play-once)
        (play)))

    (bind-key (char->integer #\p) 0 (lambda (num) (play-often (max 1 num))))

`stop-playing-region-hook (n)` | progn
| called when a region finishes playing.
`stop-playing-selection-hook ()` | progn
| called when the current selection finishes playing.
`transform-hook (snd chn scaler)` | progn
| called just after an FFT (or spectrum) is calculated.

    (add-hook! transform-hook
      (lambda (snd chn scale)
        (if (and (graph-transform? snd chn)
                 (= (transform-graph-type snd chn) graph-transform-once))
    	(report-in-minibuffer
    	 (number->string (/ (* 2.0 (vct-peak (transform-samples->vct snd chn)))
                                (transform-size snd chn)))
    	 snd)
          #f)))

    (define fft-peak
      (lambda (snd chn scale)
        (if (and (graph-transform?)
                 (= (transform-graph-type) graph-transform-once))
    	(let* ((samps (transform-samples snd chn))
    	       (len (vector-length samps))
    	       (mx (vector-ref samps 0))
    	       (peak (do ((i 1 (+ i 1)))
                             ((= i len)
                              (/ (* 2 mx) (transform-size)))
    		       (let ((val (abs (vector-ref samps i))))
    			 (if (> val mx)
                                 (set! mx val))))))
    	  (report-in-minibuffer (number->string peak) snd))
          #f)))

    (add-hook! transform-hook fft-peak)

####

      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. edit-hook is called just before any attempt to edit the channel's data; if it returns #t, the edit is aborted. So,

      (add-hook! (edit-hook) (lambda () #t))

| aborts 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. You can use edit-hook to set up protected portions of the edit history:

    (define protect
      "(protect &optional snd chn) disallows any edits before the current one"
      (lambda args
        (let* ((edit-pos (apply edit-position args))
               (hook (apply edit-hook args)))
          (reset-hook! hook)
          (add-hook! hook
            (lambda ()
              (let ((val (< (apply edit-position args) edit-pos)))
                (if val (report-in-minibuffer "protected"))
                val))))))

    (define unprotect
      "(unprotect &optional snd chn) allows edits at any edit history position"
      (lambda args
        (reset-hook! (apply edit-hook args))))

enved.scm uses several of these hooks to implement an envelope editor in lisp. See also menu-hook below. 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))))

For example, say we pile up some random stuff on name-click-hook:

    (add-hook! name-click-hook snd-print)
    (add-hook! name-click-hook (lambda (n) (snd-print n)))
    (add-hook! name-click-hook (let ((ha 32)) (lambda (n) ha)))

Then we go skiing for a week, get home, and can't remember where we were. Do we panic and dial 911? No! We simply type:

    :(describe-hook name-click-hook)
    #<primitive-procedure snd-print>
    #<procedure (n) (snd-print n)>
    #<procedure (n) ha>
    #<unspecified>
    :

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. I'm slowly replacing many of the global variables with hooks, since the latter are much more flexible. hooks.scm has snd-hooks and reset-all-hooks.

### Snd's objects

Snd presents its various data structures to the user 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 structure and functions, each aimed at a particular kind of use. One of the most commonly used is the vct. But before launching into vcts, I need to explain a few things about the following documentation.

In the following lists, optional arguments are in italics (although netscape sometimes displays them in bold face for some reason). Each sound has an associated "index" used to refer to it in all the functions. This somewhat arbitrary number is more or less related to the sound's position in the display of sounds (set the variable show-indices to cause it to be 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, 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, either use #f as the sound index or the function selected-sound.

If the snd argument is a list, the first element of the list is taken to be a mix id number, and the reference is to the underlying mix input sound data. That is, (frames 1) returns the number of frames (samples per channel) in the sound whose current index is 1; (frames (list 1)) returns the frames in the sound that underlies the mix whose id is 1. Similarly (scale-by .5) scales the currently selected sound by .5; (scale-by .5 (list 0) 2) scales the 3rd channel of mix 0's input sound by .5. I keep saying "underlying" because normally a mix is viewed after it has gone through its panel of controls (the set of widgets displayed in the Mix Panel dialog -- these can affect the amplitude, amplitude envelope, and sampling rate) -- next-mix-sample, for example, refers to the processed form of the mix data, whereas next-sample would refer to the original form. I may extend this to accept a list of (more than one) mixes (i.e. a "track" in the terminology of mix.scm).

In many cases, the snd, chn, and reg arguments can be #t (for backwards compatibility, a few of them default to #t). #t means "all" in this case; if snd is #t, all sounds are included, so, for example, `(expand-control #t)` returns a list of the current control panel expansion settings of all sounds, and `(set! (graph-transform? #t #t) #t)` turns on the fft display in all channels of all sounds.

When an error occurs, in most cases the function throws a tag such as 'no-such-sound, 'impossible-bounds, 'no-active-selection, etc. The error tag is mentioned with the function in the form [no-such-channel]. 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; to reduce clutter, I'll omit mention of these below.

### 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. This is enough of a difference to warrant the added complexity, I think. A vct object can be viewed as a vector; to make one, call make-vct. It will be 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 (the "!" appended to the setter functions is standard in Scheme; another is the use of "?" where Common Lisp is more likely to use "-p"). Once created, a vct object can be passed to a variety of built-in functions:

      (define hi (make-vct 100))
      (vct-fill! hi 3.14)
      (vct-scale! hi -1.0)

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

`list->vct (lst)` | return vct object with elements of list lst
`vct args` | list->vct with args not list: `(vct 1 2 3)` = `(list->vct '(1 2 3))`
`make-vct (len)` | create vct struct of size len.
`sound-data->vct (sdobj chan vobj)` | place sound-data channel data in vct, returning vobj
`vct? (vobj)` | #t if vobj is a vct struct.
`vct-add! (vobj1 vobj2 off)` | `vobj1[i+off] += vobj2[i]`, returns vobj1.
`vct-copy (obj)` | return a copy of obj.
`vct-do! (vobj proc)` | `vobj[i] = (funcall proc i)`.
`vct-fill! (vobj val)` | `vobj[i] = val`, returns vobj.
`vct-length (vobj)` | length of data array in vobj.
`vct-map! (vobj proc)` | `vobj[i] = (funcall proc)`.
`vct-move! (vobj new old back)` | `v[new++] = v[old++]`, returns v (if back, `v[new--] = v[old--]`)
`vct-multiply! (vobj1 vobj2)` | `vobj1[i] *= vobj2[i]`, returns vobj1.
`vct-offset! (vobj val)` | `vobj[i] += val`, returns vobj.
`vct-peak (vobj)` | abs max val in vobj
`vct-ref (vobj pos)` | value in vobj's data at location pos.
`vct-scale! (vobj scl)` | `vobj[i] *= scl`, returns vobj.
`vct-set! (vobj pos val)` | set vobj's data at location pos to val (same as `(set! (vct-ref ...) val)`).
`vct-subtract! (vobj1 vobj2)` | `vobj1[i] -= vobj2[i]`, returns vobj1.
`vct->channel (v beg dur snd chn edpos)` | The regularized version of vct->samples.
`vct->list (v1)` | return list with elements of vct v1
`vct->samples (samp samps data snd chn)`

A synonym for set-samples, but you can also pass just a vct as the first argument, or a start sample and a vct as the second argument.

    (define v (samples->vct))
    (vct-scale! v 2.0)
    (vct->samples v)

`vct->sound-data (vobj sdobj chan)` | place vct data in sound-data, returning sdobj
`vct->sound-file (fd vobj vals)` | write vals floats from vobj to fd.
`vcts-do! (vobj... proc)` | `vobj[vi][i] = (nth vi (funcall proc num i))`.
`vcts-map! (vobj... proc)` | `vobj[vi][i] = (nth vi (funcall proc num))`.
`vector->vct (vect)` | return vct object with elements of vector vect
`vct-subseq (vobj start end nv)` | return vct (nv) with vobj[start..end].

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

      region-samples->vct (samp samps reg chn v)
      samples->vct (samp samps snd chn v pos)
      transform-samples->vct (snd chn v)

There are four slightly unusual functions in this family: vct-map!, vct-do!, vcts-map!, and vcts-do!. These are essentially providing a do-loop over one or more vcts, calling some Scheme function to get the values to assign into the vct(s). For example

      (vct-map! out-data (lambda ()
                           (convolve cnv (lambda (dir)
                                           (next-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. vct-do! is the same as vct-map! except that the called function should take one argument, the current loop index. Similarly, vcts-map! and vcts-do! take any number of vcts, followed by a trailing function, and map the function's results (assumed to be a list that matches the current number of vcts) into the vct array. In the map! case, the function takes one argument, the current number of vcts awaiting values; in the do! case, it takes two arguments, the vct number and the current loop index. For example, we could rewrite the cnvtest function to take stereo sounds:

    (define cnvtest
      (lambda (snd0 snd1 amp0 amp1)
        (if (and (= (channels snd0) 2)
    	     (= (channels snd1) 2))
    	(let* ((flt-len (frames snd0))
    	       (total-len (+ flt-len (frames snd1)))
    	       (cnv10 (make-convolve :filter (samples->vct 0 flt-len snd0 0)))
    	       (cnv11 (make-convolve :filter (samples->vct 0 flt-len snd0 1)))
    	       (sf10 (make-sample-reader 0 snd1 0))
    	       (sf11 (make-sample-reader 0 snd1 1))
    	       (out-data10 (make-vct total-len))
    	       (out-data11 (make-vct total-len)))
    	  (vcts-map! out-data10 out-data11
    		     (lambda (num)
    		       (list
    			(convolve cnv10 (lambda (dir) (next-sample sf10)))
    			(convolve cnv11 (lambda (dir) (next-sample sf11))))))
    	  (free-sample-reader sf10)
    	  (free-sample-reader sf11)
    	  (vct-scale! out-data10 amp0)
    	  (vct-scale! out-data11 amp1)
    	  (vct->samples 0 total-len out-data10 snd1 0)
    	  (vct->samples 0 total-len out-data11 snd1 1))
    	(snd-print "oops -- need stereo input"))))

We can use vcts to write new sound files:

      open-sound-file (name chans srate comment)   ; returns fd
      vct->sound-file (fd vct vals)                ; writes vals floats to fd
      close-sound-file (fd vals)

After opening the file, loop through the data calling samples->vct, deal with the vct data as desired, write the samples to the file via vct->sound-file, then when finished, close-sound-file. If the new data is to replace the old, call (set! (samples...) data) with the new sound file's name; otherwise call insert-samples.

If you have Guile 1.4.1 or later, it's possible to access a vct object's elements with the syntax (obj index), equivalent to (vct-ref obj index). This is using a feature called "applicable smobs" in Guile. The clm generators also use this syntax:

      >(define hi (make-oscil 440.0))
      #<unspecified>
      >(hi)
      0.0
      >(oscil hi)
      0.125050634145737

It's no accident that the generator's type (i.e. oscil or whatever) is hidden here. For example, we can make a generator that is either an oscil or a sawtooth-wave:

      >(define sine-or-sawtooth
         (lambda (sine)
            (let ((gen ((if sine make-oscil make-sawtooth-wave) 440.0)))
               (lambda (fm)
                  (gen fm)))))
      #<unspecified>
      >(define osc (sine-or-sawtooth #t))
      #<unspecified>
      >(osc 0.0)
      0.0
      >(osc 0.0)
      0.125050634145737

### Sound-data and Sndlib

Another sound data object is the sound-data array used in Sndlib.

`make-sound-data (chans frames)` | return a sound-data object with chans arrays, each of length frames
`sound-data-ref (obj chan frame)` | return (as a float) the sample in channel chan at location frame
`sound-data-set! (obj chan frame val)` | set obj's sample at frame in chan to (the float) val
`sound-data? (obj)` | #t if obj is of type sound-data
`sound-data-length (obj)` | length of each channel of data in obj
`sound-data-maxamp (obj)` | list of maxamps of data in obj
`sound-data-chans (obj)` | number of channels of data in obj
`sound-data->vct (sdobj chan vobj)` | place sound-data channel data in vct
`vct->sound-data (vobj sdobj chan)` | place vct data in sound-data

In fact, all of the underlying sound library (Sndlib) functions are available, as well as all of (CLM). The most important Sndlib functions for Snd are:

`mus-audio-close (line)` | close audio port line
`mus-audio-describe ()` | describe audio hardware state (in help window)
`mus-audio-error ()` | returns error code indicated by preceding audio call
`mus-audio-error-name (er)r` | string decription of error code
`mus-audio-mixer-read (device field channel vals)`
| read current state of device's field:

      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

`mus-audio-mixer-write (device field channel vals)`
| set state of device's field.

    ;;; here we get the microphone volume, then set it to .5
    (define vals (make-vector 32))
    (mus-audio-mixer-read mus-audio-microphone mus-audio-amp 0 vals)
    (vector-ref vals 0)
    (vector-set! vals 0 .5)
    (mus-audio-mixer-write mus-audio-microphone mus-audio-amp 0 vals)

`mus-audio-open-input (device srate chans format bufsize)`
| open audio port device ready for input. 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)`
| open audio port device ready for output
`mus-audio-outputs (speakers headphones line-out)`
| (Sun only) set active outputs
`mus-audio-read (line sdata frames)` | read frames of data into sound-data object sdata from port line
`mus-audio-write (line sdata frames)`
| write frames of data from sound-data object sdata to port line
`mus-data-format-name (format)` | e.g. "16-bit big endian linear", format an integer, some of which have names (mus-out-format is the fastest):

      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-expand-filename (name)` | expands name into its 'absolute' pathname; that is, it replaces '~' with the current home directory, and whatever else seems appropriate.
`mus-header-type-name (type)` | e.g. "AIFF", type is an integer, some of which have names:

      mus-next  mus-aifc  mus-riff  mus-nist  mus-raw  mus-ircam  mus-aiff mus-bicsf mus-soundfont mus-voc mus-svx

`mus-sound-chans (filename)` | number of channels (samples are interleaved)
`mus-sound-close-input (fd)` | close sound file
`mus-sound-close-output (fd bytes)` | close sound file and update its length indication, if any
`mus-sound-comment (filename)` | header comment, if any
`mus-sound-data-format (filename)` | data format (e.g. mus-bshort)
`mus-sound-data-location (filename)` | location of first sample (bytes)
`mus-sound-duration (filename)` | duration of sound in seconds
`mus-sound-frames (filename)` | frames of sound according to header (can be incorrect)
`mus-sound-header-type (filename)` | header type (e.g. mus-aifc)
`mus-sound-length (filename)` | true file length (bytes)
`mus-sound-loop-info (filename)` | loop info (for an example, see examp.scm mark-loops)
`mus-sound-maxamp (filename)` | returns a list of max amps and locations thereof. The corresponding set! procedure is mus-sound-set-maxamp (this affects only the sndlib table of sound file info, not the sound file itself).
`mus-sound-maxamp-exists? (filename)`
| returns #t if the sound's max amps are already available.
`mus-sound-open-input (filename)` | open filename (a sound file) returning an integer ("fd" below)
`mus-sound-open-output (filename srate chans data-format header-type comment)`
| create a new sound file with the indicated attributes, return "fd"
`mus-sound-read (fd beg end chans sdata)`
| read data from sound file fd loading the data array from beg to end. sdata is a sound-data object that should be able to accommodate the read
`mus-sound-reopen-output (filename chans data-format header-type data-location)`
| reopen (without disturbing) filename, ready to be written
`mus-sound-report-cache (file)` | prints the current sound header data table to the file given or stdout if none is specified.
`mus-sound-samples (filename)` | samples of sound according to header (can be incorrect)
`mus-sound-seek (fd offset origin)` | complicated -- see mus_sound_seek in sndlib.html
`mus-sound-seek-frame (fd frame)` | move to frame in sound file fd
`mus-sound-srate (filename)` | sampling rate
`mus-sound-write (fd beg end chans sdata)`
| write data to sound file fd
`mus-sound-write-date (filename)` | sound's write date:

        :(strftime "%d-%b %H:%M %Z" (localtime (mus-sound-write-date "oboe.snd")))
        "18-Oct 06:56 PDT"

`set-oss-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 `(set-oss-buffers 16 12)`. This default makes the control panel controls very sluggish. Snd used to call `(set-oss-buffers 4 12)` as its default, but this seems to cause trouble for a variety of new sound cards. My initialization file includes `(set-oss-buffers 2 12)`.

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

### Sample-readers

The simplest data access function is sample, returning 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 access the sample, which can sometimes involve opening, reading, and closing a sound file. The result is that in some cases, sample will bring your function 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 (samples->vct), and the other involves the use of a special object known as a sample-reader. The former is better if you're jumping around in the data, the latter if you're treating it generally as a stream of samples, read in order. The expsrc function below shows how to use samples->vct; there are numerous examples of sample-readers in examp.scm. The basic idea is that you create a reader (via make-sample-reader or make-region-sample-reader) giving it the start position, the sound and channel to read, and the initial read direction, then get data via next-sample and previous-sample, finally closing the reader with free-sample-reader. It is not strictly necessary to call free-sample-reader yourself; the garbage collector will take care of it if you forget to. A standard way to add something to the current data is:

       ...
       (sf (make-sample-reader start))
       ...
       (vct-map! out-data (lambda () (+ (next-sample sf) <new stuff>))
       (free-sample-reader sf)
       (vct->samples start len out-data)

This is largely equivalent to the clm call

      (outa (+ start i) <new-stuff>)

but is applied as an edit to the current state in Snd. In general, slow things in Guile are vector references, floating point operations, and any do loop executed on a per-sample basis. In most cases, these can be collected into vct operations or through the use of functions like formant-bank.

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, next-mix-sample returns the next sample (before it is mixed into the output), and free-mix-sample-reader releases a reader. Mixes can be collected into tracks, so there are also make-track-sample-reader, track-sample-reader?, next-track-sample, and free-track-sample-reader. As mentioned above, the mix-sample-reader refers to the mix data after it has been processed through its control panel. The original (possibly edited) sound can be accessed by a normal sample-reader.

`free-mix-sample-reader (obj)`
| release mix-sample-reader obj.
`free-sample-reader (obj)` | release sample-reader obj.
`free-track-sample-reader (obj)`
| release track-sample-reader obj.
`make-mix-sample-reader (mix)`
| create a mix-sample-reader object reading mix
`mix-sample-reader? (obj)` | #t if obj is a mix-sample-reader.
`make-region-sample-reader (start reg chn dir)`
| create a sample-reader object reading region reg's channel chn. It is not safe to assume this reader will return zeros beyond the region boundaries.
`make-sample-reader (start snd chn dir pos)`
| create a sample-reader object reading snd's channel chn starting at sample start with initial read direction dir (1=forward, -1=backward). pos is the edit history position to read (defaults to current position). One use of pos 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 (1- edit-pos))))
          (lambda (x)
            (- x (next-sample previous-edit)) #f))))

    (map-chan (snd-diff))

| 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-track-sample-reader (track samp snd chn)`
| create a track-sample-reader object reading track
`next-mix-sample (obj)` | return next sample read by mix-sample-reader obj.
`next-sample (obj)` | return next sample read by sample-reader obj.
`next-track-sample (obj)` | return next sample read by track-sample-reader obj.
`previous-sample (obj)` | return previous sample in stream read by sample-reader obj.
`sample-reader-at-end? (obj)` | #t if sample-reader obj is at EOF.
`sample-reader-home (obj)` | returns a list with the sound index and channel number associated with obj.
`sample-reader? (obj)` | #t if obj is a sample-reader.
`track-sample-reader? (obj)` | #t if obj is a track-sample-reader.

The next-sample functions can be omitted: `(reader)` is the same as `(next-sample reader)`.

### 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, for example).

`add-mark (sample snd chn)` | add mark at sample, returning mark id.
`backward-mark (count snd chn)` | move back count marks (C-j), returning mark id, or #f if none.
`delete-mark (id)` | delete mark id (- C-m).
`delete-marks (snd chn)` | delete all marks in snd's channel chn
`find-mark (samp snd chn)` | return identifier of mark at sample samp or #f if none found. This identifier is used in calls such as mark-sample. Since marks can move around during editing, a unique 'tag' is needed to refer to a particular mark. samp can also be a string; in this case find-mark looks for a mark of that name.
`forward-mark (count snd chn)` | move forward count marks (C-j), returning mark id, or #f if none.
`mark-click-hook (id)` | called when a mark is clicked, return #t to squelch message.
`mark-name (id)` | name of mark id; also (set! (mark-name id) name).
`mark-sample (id pos)` | sample (number) marked by mark id at edit history position pos; also `(set! (mark-sample id) samp)`. 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.
`mark-sync (id)` | mark id's sync value; also (set! (mark-sync id) sync).
`mark-sync-max ()` | max mark sync value seen so far.
`mark-home (id)` | sound and channel that hold mark id.
`marks (snd chn pos)` | list of mark ids in snd's channel chn at edit history position pos. If chn and pos 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))))

`mark? (id)` | #t if mark id is active.
`save-marks (snd)` | save snd's marks, writing a file <name>.marks (returns file name or #f if no marks).
`syncd-marks (sync)` | list of marks (ids) that share sync.

The sync value is very similar to the mix track number or the sound sync field; it 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, etc. mark-home provides a way to go from a mark id to the sound and channel that hold that mark; the inverse function is marks. To find which marks share a given sync value, use syncd-marks; to find an unused sync value use mark-sync-max.

    (define (move-syncd-marks sync diff)
      (map (lambda (m)
             (set! (mark-sample m) (+ (mark-sample m) diff)))
           (syncd-marks sync)))

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. So, for example, the following procedure returns a list describing the history of a mark over the course of editing a channel, each list entry being the mark sample at that time or #f if the mark was not active:

    (define (describe-mark id)
      (let ((mark-setting (mark-home id)))
        (if (eq? mark-setting 'no-such-mark)
    	;; not an active mark, so go scrounging for it
    	;;   we're looking at every edit of every channel
    	(do ((i 0 (1+ i)))
    	    ((or (= i (max-sounds)) (list? mark-setting)))
    	  (if (sound? i)
    	      (do ((j 0 (1+ j)))
    		  ((or (= j (channels i)) (list? mark-setting)))
    		(let* ((max-edits (apply + (edits i j))))
    		  (do ((k 0 (1+ k)))
    		      ((or (> k max-edits) (list? mark-setting)))
    		    (if (member id (marks i j k))
    			(set! mark-setting (list i j)))))))))
        (if (list? mark-setting)
    	(let* ((snd (car mark-setting))
    	       (chn (cadr mark-setting))
    	       (max-edits (apply + (edits snd chn)))
    	       (descr '())
    	       (header (list 'mark id 'sound snd (short-file-name snd) 'channel chn)))
    	  (do ((i max-edits (1- i)))
    	      ((< i 0) descr)
    	    (if (member id (marks snd chn i))
    		(set! descr (cons (mark-sample id i) descr))
    		(set! descr (cons #f descr))))
    	  (cons header descr))
    	'no-such-mark)))

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 uses the mark-clicked-hook instead; you type (start-sync), then click the set of marks to sync, 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 move together deleting data, or inserting zeros; or click the "play" triangle, and all play together starting from the respective marks (which need not be in separate channels). See marks.scm for more examples.

### Mixes

A mix is an object that controls one channel of a sound mix. All of the signal-processing functions can be applied to the mix data. These variables affect all mixes:

`mix-color dark gray`

color of mix waveforms.
The set form `(set! (mix-color) ...)` has an optional second argument; if you want to set just a particular mix's color, give the id of the mix here. That is, `(set! (mix-color) red)` sets all unselected mixes to red; but `(set! (mix-color 3) red)` sets only mix #3 to be red. Similarly, mix-color has an optional argument that can specify which mix's color we want.

`selected-mix-color light green` | color of selected mix waveform.
`mix-waveform-height 20` | Max height (pixels) of mix waveforms (see show-mix-waveforms).
`with-mix-tags #t` | If #f, don't make mixes editable.
`show-mix-waveforms` |

Each mix object has a unique identifier used in the next set of functions to access a particular mix object:

`backward-mix (count snd chn)` | move back count mix tags (C-x C-j), returning the mix id.
`find-mix (samp snd chn)` | return identifier of mix at sample samp (or anywhere in the given channel if samp is not specified) or #f if no mix is found.
`forward-mix (count snd chn)` | move forward count mix tags (C-x C-j), returning the mix id.
`mixes (snd chn pos)` | list of currently active mixes in snd's channel chn at history pos.
`mix-amp (mix chan)` | amplitude of mix's channel chan.
`mix-amp-env (mix chan)` | amplitude envelope of mix's channel chan.
`mix-anchor (mix)` | anchor position (within the mix) of mix.
`mix-chans (mix)` | chans in mix.
`mix-length (mix)` | length in samples of mix.
`mix-locked (mix)` | #t if mix is locked. A mix is automatically locked (i.e. made immovable) if an edit operation affects some portion of the data that the mix also affects. For example, if you delete a portion of a sound that has actively mixed data, the associated mix tag goes away until you undo that deletion. This can be used deliberately to remove a mix:

    (define (delete-mix id)
      (if (mix? id)
          (as-one-edit
           (lambda ()
    	 (do ((i 0 (1+ i)))
    	     ((= i (mix-chans id)))
    	   (set! (mix-amp id i) 0.0))
    	 (set! (mix-locked id) #t)))))

`mix-name (mix)` | mix's name.
`mix-position (mix)` | position (sample number) of mix.
`mix-home (mix)` | returns a list of the sound index and channel number affected by mix.
`mix-speed (mix)` | speed of mix.
`mix-tag-width (mix)` | tag width in graph of mix (default 6)
`mix-tag-height (mix)` | tag height in graph of mix (default 14)
`mix-tag-y (mix)` | tag y offset in graph of mix (default 0)
`mix-track (mix)` | mix track (0 = none). (mix-sync is a synonym for this).
`mix? (mix)` | #t if mix is active.
`play-mix (mix)` | play mix mix.
`play-track (track snd chn)` | play track track. If snd is #t, play all associated chans.
`selected-mix ()` | selected mix (-1 = none) -- set to select mix.
`select-mix (mix)` | set selected mix (-1 = none).

As an example, say we have a mix active whose "id" is 123.

    >(mix-chans 123)
    1
    >(set! (mix-amp 123 0) .5)
    .5

This sets mix 123's channel 0 amplitude scaler to .5. To mix data in such a way as to create an associated mix object:

`mix (file samp in_chan snd chn with-mix-tags)`

mix file's channel in_chan starting at samp in snd's channel chn. if only the file argument is given, this is equivalent to the File menu's Mix option; the mix start sample in this case depends on the cursor location. mix returns the id of the first channel's mix (subsequent channels simply increment this number). id of -1 means some error occurred. If with-mix-tags is #f (default is #t), the data is simply mixed without creating any mix tags [no-such-file].

`mix-region (samp reg snd chn)`
| Mix in region reg at sample samp (defaulting to the cursor sample), in snd's channel chn. mix-region returns the id of the first channel's mix (subsequent channels simply increment this number). [no-such-region]
`mix-sound (file start)`
| mix file into selected sound at start [no-such-file].
`mix-vct (vct beg snd chn with-mix-tags origin)`
| mix the contents of vct into snd's channel chn starting at frame beg. Returns the id of the new mix, or -1 if some error occurred. If with-mix-tags is #f (default is #t), the data is simply mixed without creating any mix tags, and without returning a mix id.

In the functions that refer to a given sound, the form `(list mix-id)` refers to the underlying mix's sound. So, for example, `(src-sound 2.0 1.0 (list 0) 0)` performs sampling-rate conversion on mix 0's sound's channel 0; `(display-edits (list 0) 0)` shows the edit list of that sound, and `(set! (edit-position (list 0) 0) 0)` reverts it to its original state.

### Regions and Selections

A region is a saved portion of sound data. There is a dialog to browse regions, and a notion of a stack of regions. As regions are added, old ones get pushed off the stack and deleted. The current selection is usually based on region 0, although in fact there's no necessary connection between them. Each region has a unique id returned by make-region and shown beside the region name in the Region Browser. Most of the region number arguments default to the current region (the first in the regions list).

`forget-region (reg)` | "forget" region number reg, removing it from the region stack. Once forgotten, the region's data no longer exists.
`insert-region (beg reg snd chn)` | insert region reg at sample beg in snd's channel chn.
`make-region (beg end snd chn)` | create a new region spanning samples beg to end in snd's channel chn. returns region's id. If no arguments are given, the current selection is used.
`make-region-sample-reader (start reg chn dir)`
| create a sample-reader object reading region reg's channel chn
`mix-region (samp reg snd chn)` | see above.
`play-region (reg wait)` | play region reg, if wait is #t, play to end before returning.
`protect-region (reg protect)` | protect/unprotect region reg from deletion (removal from the region stack).
`region-chans (reg)` | number of channels in region reg.
`region-graph-style (style)` | graph drawing choice for the region dialog's graph.
`region-length (reg)` | number of samples (per channel) in region reg.
`region-maxamp (reg)` | maximum amplitude of region reg.
`region-sample (samp reg chn)` | value of sample samp in region reg in channel chn.
`region-samples (samp samps reg chn)`
| return vector of samps samples starting at samp in region reg's channel chn.

    (define region-rms
     (lambda (n)
      (let* ((data (region-samples 0 0 n)) ; len=0 => entire region
    	 (len (vector-length data))
    	 (sum 0.0))
       (do ((i 0 (1+ i)))
    	((= i len) (sqrt (/ sum len)))
        (set! sum (+ sum (* (vector-ref data i) (vector-ref data i))))))))

`region-samples->vct (samp samps reg chn v)`
| return vct struct of samps samples starting at samp in region reg's channel chn. If v (a vct object) is provided, it is filled, rather than creating a new vct object.
`region-srate (reg)` | original (nominal) sampling rate of region reg.
`regions ()` | list of ids of regions currently active. The most recently created region is `(car (regions))`. `(map region-length (regions))` returns a list of region lengths.
`region? (reg)` | #t if region reg exists (has not yet been forgotten).
`save-region (reg filename type format comment)`
| save region reg in filename in data format format (default mus-bshort), header type type (defaults to mus-next), and comment. [cannot-save].

These are specific to the current selection (these can return the error 'no-active-selection):

`convolve-selection-with (file amp)`
| convolve the current selection with file
`delete-selection ()` | deletes currently selected portion, if any.
`env-selection (envelope env-base)`
| apply envelope to the selection. envelope can also be a CLM env generator (in this case, env-base is ignored).
`filter-selection (env order)` | apply an FIR filter of order order and frequency response env to the current selection. env can be the filter coefficients themselves in a vct object with at least order elements, or a CLM filtering generator (see filter-sound).
`insert-selection (beg snd chn)` | insert (paste) selection starting at beg in snd's channel chn.
`mix-selection (beg snd chn)` | mix (add) selection starting at beg in snd's channel chn. Returns new mix id.
`play-selection (wait pos)` | play the currently selected data. If pos is given, it sets the edit position to be played.
`reverse-selection ()` | reverse data delineated by current selection
`save-selection (file header-type data-format srate comment chan)`
| save the currently selected data in file. If chan is given, save only that channel.
`scale-selection-by (scalers)` | scale the current selection by scalers which can be either a float, or a vector of floats
`scale-selection-to (scalers)` | normalize the current selection to scalers which can be either a float, or a vector of floats
`select-all (snd chn)` | selects all samples in snd's channel chn. If a region is created, it returns the region's id.
`selection-chans ()` | selection channels
`selection-length (snd chn)` | selection frames. See make-selection in extensions.scm.
`selection-member? (snd chn)` | #t if snd's chn is member of active selection. (This is settable). See make-selection in extensions.scm. If snd is #t and the new value is #f, the entire current selection is deactivated.
`selection-position (snd chn)` | selection begin sample number (used to be called selection-beg). See make-selection in extensions.scm.
`selection-srate ()` | selection srate
`selection? ()` | #t if selection is active.
`smooth-selection ()` | apply a smoothing function to the current selection. This produces a sinusoid between the end points.
`src-selection (num-or-env base)`
| same as src-sound but applied to current selection.

The selected portion can be set, independent of any region, by setting selection-position and selection-length. See make-selection in extensions.scm.

### 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. To make global operations simpler to achieve, some of the snd and chan arguments can be #t, referring to all current sounds or all channels of a sound; of these, some default to the currently selected sound if no argument is given, whereas others (mainly for historical reasons) default to all sounds and channels; the former are indicated as snd or snd chn below, and the latter as snd or snd chn. Also, in most of the signal-processing functions, the snd argument can also be a list; in this case it refers to a mix. For example, the cursor function, which refers to the current cursor location, is listed here as:

      cursor           (snd chn)     cursor location

which indicates that `(cursor 0 0)` returns the cursor location in sound 0, channel 0, `(cursor)` returns the location in the currently selected sound, `(cursor #t #t)` returns a list of lists of all the cursor locations, and `(set! (cursor) 0)` sets (just) the cursor in the currently selected channel; on the other hand, the transform-size function is listed as:

      transform-size         (snd chn)     FFT size.

which means that `(transform-size 0 0)` returns the fft size used in sound 0, channel 0, `(transform-size)` returns the globally effective fft size, `(transform-size #t #t)` returns a list of lists of all the fft sizes, and `(set! (transform-size) 512)` sets all fft sizes to 512.

The variables are local to each sound or each channel. That is, `(amp-control snd)` returns the control-panel amp setting, and `(set! (amp-control snd) val)` sets its value to val.

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.

> 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 #f 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:

>     env-sound env beg dur s c e                     env-channel clm-env-gen beg dur s c e
>     filter-sound env order s c e                    clm-channel clm-filter-gen beg dur s c e overlap
>     map-chan func beg end origin s c e              map-channel func beg dur s c e origin
>     scan-chan func beg end s c e                    scan-channel func beg dur s c e
>     play beg s c sync end e                         play-channel beg dur s c e
>     reverse-sound s c e                             reverse-channel beg dur s c e
>     scale-sound-by scl beg dur s c e                scale-channel scl 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
>     samples->vct beg dur snd chn v e                channel->vct beg dur s c e
>     vct->samples beg dur vct s c trunc origin fchan vct->channel vct beg dur s c e
>     insert-silence beg dur s c                      pad-channel beg dur s c e
>     src-sound num base s c e                        src-channel ratio-or-env-gen beg dur s c e
>     convolve-with file amp s c e                    clm-channel convolve-gen beg dur s c e
>     loop-samples reader c-func dur origin ptr       c-channel c-func beg dur s c e ptr

> An equivalent Scheme wrapper might be:

>      (define* (map-channel func #:optional (beg 0) dur snd chn edpos (edname "map-channel"))
>        (let* ((end (if dur (+ beg dur) (1- (frames snd chn edpos)))))
>          (map-chan func beg end edname snd chn edpos)))

`add-player (player start end pos)`
| add player to the play-list (see make-player). If pos is given, play at that edit position. [no-such-player]
`append-to-minibuffer (msg snd)`
| append msg to whatever is in snd's minibuffer [no-such-sound].
`backward-graph (count snd chn)`
| move back (up or left) count graphs (C-x C-o), returning a list of the new sound index and channel number.
`backward-sample (count snd chn)`
| move back count samples (C-b), return new cursor position.
`beats-per-minute (snd chn)`
| The x axis labelling of the time domain waveform can be in beats (x-axis-style = x-axis-in-beats); this variable sets the number of beats per minute. The default is 60.0, making it the same as x-axis-in-seconds.
`bomb (snd on)`
| display bomb icon in snd's minibuffer. Set on to #f to erase 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. [no-such-sound].

    (define show-bomb
      (lambda (n speed)
        (if (> n 0)
    	(begin
    	  (bomb)
    	  (in speed (lambda () (show-bomb (- n 1) speed))))
    	(bomb 0 #f))))

    (show-bomb 20 200)

`channel-amp-envs (file chan size peak-file-func work-proc-func)`

This procedure returns two vectors of length 'size' containing y vals (min and max) of file's channel chan's amp envs. '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), pts is an edit-position, and the current amp envs (if any) are returned. See make-sound-icon in snd-motif.scm for an example.

`channel-style (snd)`
| The state of the 'unite' button in multi-channel files. Values are channels-separate, channels-combined, and channels-superimposed.
`channel-sync (snd chn)`
| sync field local to channel (currently unused).
`channel->vct (beg dur snd chn edpos)`
| channel->vct is the regularized version of samples->vct.

    (define* (selection->vct #:optional snd chn)
      (if (selection-member? snd chn)
          (channel->vct (selection-position snd chn)
    		    (selection-length snd chn)
    		    snd chn)
          (if (selection?)
    	  (format #f "snd ~D channel ~D is not a member of the current selection" snd chn)
    	  "no active selection")))

`channels (snd)` `chans (snd)`
| number of channels in snd. chans is another name for this procedure.
`clm-channel (clm-gen beg dur snd chn edpos overlap)`
| apply clm-gen to snd's channel chn 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)))
    	 (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)
    				       (next-sample reader)))))
        (clm-channel cgen beg dur snd chn edpos)
        (free-sample-reader reader)))

`close-sound (snd)`
| close snd (same as File menu Close).
`comment (snd)`
| snd's comment, if any.
`convolve-with (file amp snd chn edpos)`
| convolve snd's channel chn (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) [no-such-file]. 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 (expr sample snd chn edpos)`
| return how many samples satisfy the expression expr. expr is a Scheme function of one argument (the current sample value). sample determines where to start the search. `(count-matches (lambda (y) (> y .1)))`
`cursor (snd chn)`
| cursor location (samples) of channel chn of snd.
`cursor-follows-play (snd)`
| #t if cursor is following along in the sound as it plays. To make this the default: `(add-hook! after-open-hook (lambda (sp) (set! (cursor-follows-play sp) #t)))`
`cursor-position (snd chn)`
| 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.
`cursor-size (snd chn)`
| cursor size (defaults to 15).
`cursor-style (snd chn)`
| cursor style (cursor-cross or cursor-line) The normal shape is a "+" sign; the cursor-line is a vertical line. The following hooks set the cursor-style to cursor-line while playing if cursor-follows-play is #t:

    (define set-sound-cursor
      (lambda (snd shape)
        (do ((j 0 (1+ j)))
            ((= j (channels snd)) #f)
          (set! (cursor-style snd j) shape))))

    (add-hook! start-playing-hook
      (lambda (snd)
        (if (cursor-follows-play snd)
            (set-sound-cursor snd cursor-line))))

    (add-hook! stop-playing-hook
      (lambda (snd)
        (set-sound-cursor snd cursor-cross)))

| The cursor-style can also be a procedure of three arguments, the sound index, channel number, and graph (always time-graph). The procedure should draw the cursor at the current cursor position using the cursor-context whenever it is called. For example, the following 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)))

`data-format (snd)`

snd's data format (e.g. mus-bshort). If you encounter a file with an unknown format, or a header that has the wrong format, you can set this field forcing Snd to interpret the data in any form 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, try changing these settings. For example, some NeXT/Sun (au) header files using the data format for 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 these 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. If you set any of these fields, it is possible that the sound's index will change (there is an embedded update-sound).

`data-location (snd)`
| snd's data location (bytes).
`delete-sample (samp snd chn edpos)`
| delete sample samp in snd's channel chn [no-such-sample].
`delete-samples (samp samps snd chn edpos)`
| delete samps samples starting at sample samp.
`dot-size (snd chn)`
| size in pixels of dots when graphing with dots (default: 1)
`env-channel (clm-env-gen beg dur snd chn edpos)`
| env-channel is the regularized version of env-sound. clm-env-gen must be a CLM envelope generator.
`env-sound (envelope samp samps env-base snd chn edpos)`

apply (in amplitude) envelope to snd's channel chn starting at sample samp for samps samples with connecting segments based on env-base. env-base defaults to 1.0 (line segments). samp defaults to 0. samps defaults to the full duration. envelope is a list or vector containing the breakpoint values (as in CLM).
`(env-sound '(0 0 1 1 2 0))`
envelope can also be a CLM env generator (in this case, env-base is ignored, and it's assumed samps is reflected in the make-env call).

`fft-log-frequency (snd chn)`
| spectrum frequency axis is logarithmic (#t) or linear (#f) (default #f).
`fft-log-magnitude (snd chn)`
| spectrum magnitude axis is in decibels (#t) or linear (#f) (default #f).
`fft-window (snd chn)`

fft data window (default: blackman2-window)

    rectangular-window hann(ing)-window   welch-window      parzen-window
    bartlett-window    hamming-window     blackman2-window  blackman3-window
    blackman4-window   exponential-window riemann-window    kaiser-window
    cauchy-window      poisson-window     gaussian-window   tukey-window
    dolph-chebyshev-window (this only if GSL is loaded)

`fft-window-beta (snd chn)`
| The fft data window parameter, if relevant.
`file-name (snd)`
| snd's complete file name.
`filter-sound (env order snd chn edpos)`

apply an FIR filter of order order and frequency response env to snd's channel chn. env can be the filter coefficients themselves, in a vct object with at least order elements (see gmeteor). It can also be 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. (The regularized version of this is clm-channel).

    (filter-sound '(0 1 1 0) 1024)                       ;FIR filter given frequency response
    (filter-sound (list->vct '(.1 .2 .3 .3 .2 .1)) 6)    ;FIR filter given actual coefficients
    (filter-sound (make-fir-filter 6 (list->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 .99 1200))               ;CLM formant

`find (expr sample snd chn edpos)`
| find the sample that satisfies the Scheme function expr. sample determines where to start the search. If the function returns an integer, it represents how many samples to offset the cursor from the "find" location. `(find (lambda (y) (> y .1)))` `(find (lambda (y) (and (> y .1) -2)))`
`find-sound (filename)`
| returns the index of filename (used as snd throughout). returns #f if no sound is found that matches filename.
`finish-progress-report (snd)`
| see progress-report.
`forward-graph (count snd chn)`
| move forward (down or right) count graphs (C-x C-o), returning a list of the new sound index and channel number.
`forward-sample (count snd chn)`
| move forward count samples (C-f), return new cursor position.
`frames (snd chn edpos)`
| chn's current length in samples. Used with set!, this either truncates the sound or pads it with zeros at the end.
`graph (data xlabel x0 x1 y0 y1 snd chn force-display)`

Display 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 (default 0 to 1.0), the y axis goes from y0 to y1 (default fits the data), and the display is associated with channel chn in snd. `(graph #(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 vectors or vcts; each is graphed at the same time, following the sequence of colors used when channels are superimposed. See examp.scm. If data is a list of numbers, it is assumed to be an envelope (a list of breakpoints). If force-display is #f (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.

    (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)

`graph-style (snd chn)`

how sound data is displayed (default: graph-lines) The choices are:

    graph-lines  graph-dots  graph-filled  graph-lollipops  graph-dots-and-lines

It is possible to set the graph-style of each of a channel's graphs independently. The lower 8 bits of the graph-style apply to the main graph, and to the other graphs if they aren't set explicitly; the second 8 bits refers to the fft graph offset by 1, and the next 8 bits are the lisp graph. This is clumsy, but backwards compatible. For example,

    (set! (graph-style 0 0)
          (+ graph-dots (ash (+ graph-lines 1) 8)
                        (ash (+ graph-lollipops 1) 16)))

| causes the main (time domain) graph to be displayed using dots, the fft graph to use lines, and the lisp graph lollipops. There are some functions in extensions.scm (time-graph-style et al) that might make these fields easier to access and change.
`graph-lisp? (snd chn)`
| #t if lisp-generated graph is being displayed.
`graph-time? (snd chn)`
| the state of snd's channel chn's 'w' button.
`graph-transform? (snd chn)`
| #t if snd's channel chn is displaying a spectrum (the 'f' button).
`graphs-horizontal (snd chn)`
| determines whether channel graphs (time domain, spectrum, lisp graph) are arranged vertically or horizontally.
`header-type (snd)`
| snd's header type (e.g. mus-aiff).
`insert-silence (beg num snd chn)`
| insert num zeros at beg in snd's channel chn.
`insert-sound (file beg in_chan snd chn edpos)`
| insert channel in_chan of file at sample beg in snd's channel chn. beg defaults to the cursor position; if in_chan is not given, all channels are inserted. [no-such-file].
`insert-sample (samp value snd chn edpos)`
| insert sample value at sample samp in snd's channel chn [no-such-sample].
`insert-samples (samp samps data snd chn edpos)`
| insert samps samples of data starting at sample samp in snd's channel chn. data can be a filename; Snd assumes any such file is temporary; it will be deleted when no longer needed. The regularized version of this might be: `(define* (insert-channel data #:optional beg dur snd chn edpos) (insert-samples beg dur data snd chn edpos))`.
`left-sample (snd chn)`
| return the position in samples of the left edge of the time domain waveform for snd's channel chn. To get the data currently displayed in the time domain window:

    (define window-samples
     (lambda ()
      (let ((wl (left-sample))
    	(wr (right-sample)))
       (samples wl (+ 1 (- wr wl))))))

`loop-samples (reader function calls origin environ)`

This calls external C functions while moving through the data accessed by the sample reader. loop-samples takes a sample reader, a pointer to a (C) float function that takes one float argument (the current sample), the number of times to call that function, and a name for the editing operation for the edit history list. If environ is included, it is passed as the second (void *) argument to the function. See grfsnd.html for examples of both forms. The "regularized" version of this is:

    (define* (c-channel func #:optional (beg 0) dur snd chn edpos call-data)
      (let ((reader (make-sample-reader beg snd chn edpos))
            (samps (or dur (frames snd chn edpos))))
        (loop-samples reader func samps "c-channel" call-data)))

`make-player (snd chn)`

make a new player associated with snd's channel chn. 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 also add-amp-controls in sndgtk.scm, and play-syncd-marks in marks.scm.
`map-chan (func start end edname snd chn edpos)`

map-chan applies func to samples in the specified channel. As opposed to scan-chan, map-chan can change the data. An optional subsequence of the data can be requested via 'start' and 'end' points. If beg is #f, it defaults to 0; if end is #f, it defaults to the end of the channel. The fourth argument edname is the name of the editing operation that will be reported by the edit history mechanism. 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 list, vct object, or vector of numbers; the numbers 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-chan 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.

    >(map-chan (lambda (y) (+ y .2)))
    #f

    >(map-chan (lambda (y) (cos y)) #f #f "(cos y)")
    #f

    >(map-chan (lambda (y) (if (> y .1) (list .1 .2 .3) y)))
    #f

Here's a slightly more involved example; we define a function that finds silences and replaces them with something:

    (define map-silence
      ;; see squelch in effects.scm for a better version
      (lambda (silence replacement)
        (let ((sum-of-squares 0.0)
    	  (buffer (make-vector 128 0.0))
    	  (position 0)
    	  (current-sample 0)
    	  (chan-samples (frames)))
          (lambda (y)
            (let ((old-y (vector-ref buffer position)))
    	  (set! sum-of-squares (- (+ sum-of-squares (* y y)) (* old-y old-y)))
    	  (vector-set! buffer position y)
    	  (set! position (1+ position))
    	  (if (= position 128) (set! position 0))
    	  (set! current-sample (1+ current-sample))
    	  (if (> sum-of-squares silence)
    	      (if (= current-sample chan-samples)
    		  ;; at end return trailing samples as long as it looks like sound
    		  (let ((temp-buffer (make-vector 128 0.0)))
    		    (do ((i 0 (1+ i)))
    			((= i 128) temp-buffer)
    		      (let ((final-y (vector-ref buffer position)))
    			(vector-set! temp-buffer i (if (> sum-of-squares silence) final-y 0.0))
    			(set! sum-of-squares (- sum-of-squares (* final-y final-y)))
    			(set! position (1+ position))
    			(if (= position 128) (set! position 0)))))
    		  old-y)
    	    replacement))))))

    (map-chan (map-silence .01 0.0))  ; squelch background noise
    (map-chan (map-silence .001 #f))   ; remove silences altogether

In case it isn't obvious, we're using buffer to hold a running portion of the sound, and sum-of-squares to hold the sum of the squares of all the samples in that portion. When the portion's sum falls below the argument silence, we replace the current sample with replacement. At the end, we flush out all the remaining samples awaiting output in buffer.
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-chan (lambda (val)
                    (set! ctr (1+ ctr))
                    (if (> ctr 100)
                      (return "quitting!")
                      val)))))

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->samples for example). Use snd-tempnam to get a temporary filename that reflects the current temp-dir setting (if any). The env-sound-interp function in examp.scm is an example of this. snd4.scm has Snd-4 style mapping functions such as map-sound-chans, map-all-chans, etc. For example:

    (define* (map-sound-chans proc #:optional (beg 0) end edname snd edpos)
      (do ((i 0 (1+ i)))
          ((= i (chans snd)))
        (map-chan proc beg end edname snd i edpos)))

    (define* (map-all-chans proc #:optional (beg 0) end edname edpos)
      (apply for-each
    	 (lambda (snd chn)
    	   (map-chan proc beg end edname snd chn edpos))
    	 (all-chans)))

`map-channel (func beg dur snd chn edpos edname)`
| map-channel is the regularized version of map-chan.
`maxamp (snd chn edpos)`
| max amp of snd's channel chn. Used with set!, this is equivalent to scale-to.
`max-sounds ()`
| current size of sound list (grows as required).
`max-transform-peaks (snd chn)`
| max number of transform peaks reported (default: 100).
`min-dB (snd chn)`
| Sets the minimum dB value displayed in various graphs (default: -60.0).
`new-sound (name header-type data-format srate chans comment)`

create a new (empty) sound named name. If the type and other arguments are not specified, a dialog is posted to get the needed values which default to the current default-output-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-nist mus-raw mus-ircam mus-aiff mus-soundfont mus-bicsf mus-voc mus-svx

`open-raw-sound (name chans srate format)`

open name 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 if you want to ignore the header). [no-such-file].

    (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))))

`open-sound (name)`
| open name as in File menu Open option. [no-such-file].
`pad-channel (beg dur snd chn edpos)`
| insert num zeros at beg in snd's channel chn. This is the regularized version of insert-silence. A similar function for setting to zero might be: `(define (silence-channel beg dur snd chn edpos) (scale-channel 0.0 beg dur snd chn edpos))`
`peak-env-info (snd chn pos)`
| 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.
`peaks (file snd chn)`
| display fft peak information. If file is not null, write the information to that file, else post it in a help window (where it can be selected and pasted elsewhere).
`play (samp snd chn sync end pos)`

play snd's channel chn starting from sample samp. If 'sync' is #t, play all sounds syncd to snd. If 'end' is not given or #f, play until end of 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). 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). If pos is given, play at that edit position. [no-such-file, no-such-sound]
The pos argument makes it easier to try "A:B" comparisons. `(play 0 #f #f #f #f (1- (edit-position)))` plays version before the latest edit. `(define (play-then-and-now) (play-and-wait 0 #f #f #f #f (1- (edit-position))) (play))`

`play-and-wait (samp snd chn sync end pos)`
| play snd's channel chn starting from sample samp and wait for it to finish. If pos is given, play at that edit position.
`play-channel (beg dur snd chn edpos)`
| play-channel is the regularized version of play.
`player-home (player)`
| return a list of the sound index and channel number associated with player-home. [no-such-player]
`player? (obj)`
| #t is obj is an active player.
`position->x (xpos snd chn axis)`
| returns the X axis value that corresponds to the graph position xpos. [no-such-axis].
`position->y (ypos snd chn axis)`
| returns the Y axis value that corresponds to the graph position ypos. [no-such-axis].
`progress-report (pct name current-channel channels snd)`

The functions start-progress-report, progress-report, and finish-progress-report handle the animated hour-glass icon used 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 actually 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 snd's minibuffer using name and so on to identify itself.

`prompt-in-minibuffer (msg callback snd (raw #f))`
| Post msg in snd's minibuffer, and when the user responds, call callback with the response as the callback's argument. If callback is specified it should be either #f or a function of one argument. If 'raw' is #t, the response is returned as a string; otherwise it is evaluated first as Scheme code.

    (prompt-in-minibuffer "sample 0 via func:"
      (lambda (func)
        (set! (sample 0) (func (sample 0)))))
        ;; here we're assuming the user will type a function in response to the prompt
        ;;   the value returned by the function will be the new sample 0 value
        ;;   the function should take one argument, the current sample 0 value
        ;; now if the user responds: (lambda (y) (+ y .5))
        ;; sample 0 will be set to itself + .5

| 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 (which prompts for confirmation first):

    (bind-key (char->integer #\k) 0
      (lambda ()
        (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 ...

`read-only (snd)`
| #t if snd is read-only, #f otherwise.
`redo (edits snd chn)`
| redo edits edits (default is 1) in snd's channel chn.
`read-peak-env-info-file (snd chn filename)`
| opens filename, assumed to be the peak-env amp info written by write-peak-env-info-file for the given channel. This should be called only within initial-graph-hook.
`report-in-minibuffer (msg snd)`
| post msg in snd's minibuffer [no-such-sound].
`reverse-channel (beg dur snd chn edpos)`
| reverse-channel is the regularized version of reverse-sound.
`reverse-sound (snd chn edpos)`
| reverse data. There are some interesting "non-causal" effects you can get with this; for example, take a voice sound, reverse it, reverberate it, reverse it again, and you get the original with reversed reverb.
`revert-sound (snd)`
| revert snd to saved state (undo all edits).
`right-sample (snd chn)`
| position (samples) of right edge of time domain waveform.
`sample (samp snd chn edpos)`
| value of sample samp in snd's channel chn. If the desired sample happens to fall outside the current buffer for the indicated channel, this function grinds to a halt -- if you're running a loop through a bunch of samples, use the sample-readers or samples->vct instead. samp defaults to the current cursor location.
`samples (samp samps snd chn edpos)`
| return vector of samps samples starting at samp in snd's channel chn. samp defaults to 0. samps defaults to frames - samp. pos is the edit history position to read (defaults to current position). This is settable (as is sample) -- see set-samples.
`samples->sound-data (samp samps snd chn sdobj edpos sdchan)`
| similar to samples->vct, but fill a sound-data object [no-such-edit].
`samples->vct (samp samps snd chn v edpos)`
| return vct struct with same data as in samples call above. If v (a vct object) is provided, it is filled, rather than creating a new vct object. pos is the edit history position to read.
`save-sound (snd)`
| save snd; same as File menu's Save option. [cannot-save].
`save-sound-as (filename snd header-type data-format srate channel edpos)`
| save snd as filename (same as File Save as option). If channel is specified, only that channel is saved (extracted). edpos, if given, specifies which edit history position to save. Any argument can be #f which causes its value to be taken from the sound being saved. [cannot-save].
`scale-by (scalers snd chn)`
| scale 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 or a vector of floats. 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 (defaults to the currently selected channel).
`scale-channel (scl beg dur snd chn edpos)`
| scale-channel is the regularized version of scale-sound-by.
`scale-sound-by (scaler beg num snd chn edpos)`
| scales the samples in the given channel between beg and beg + num by scaler. If the channel argument is omitted, scale-sound-by scales the entire sound. beg defaults to 0; num defaults to the length of the channel. snd defaults to the selected sound, and chn to the selected channel. Unlike scale-by, this ignores the sync setting.
`scale-sound-to (norm beg num snd chn)`
| normalizes the samples in the given channel between beg and beg + num to norm. If the channel argument is omitted, scale-sound-to normalizes the entire sound. beg defaults to 0; num defaults to the length of the channel. snd defaults to the selected sound, and chn to the selected channel. Unlike scale-to, this ignores the sync setting.
`scale-to (scalers snd chn)`
| (see scale-by) -- normalize snd to scalers (following sync as in scale-by).
`scan-chan (func start end snd chn edpos)`

scan-chan applies func to samples in the specified channel. An optional subsequence of the data can be requested via 'start' and 'end' points. If beg is #f, it defaults to 0; if end is #f, it defaults to the end of the channel. The function is passed the current sample as its argument. If it returns something other than #f, the scan is stopped and a list is returned containing the non-#f value returned 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-chan (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-chan (lambda (y)
    			      (not (proc y))))))
          (if baddy (set! (cursor) (cadr baddy)))
          (not baddy))))

    >(every-sample? (lambda (y) (< y .5)))
    #t

scan-chan could be defined in terms of make-sample-reader:

    (define (scan-chan . args)
      (let* ((func (car args))
    	 (len (length args))
    	 (start (if (> len 1) (cadr args) 0))
    	 (snd (if (> len 3) (cadddr args) (selected-sound)))
    	 (chn (if (> len 4) (caddddr args) (selected-channel snd)))
    	 (end (if (> len 2) (caddr args) (frames snd chn)))
    	 (reader (make-sample-reader start snd chn))
    	 (result #f))
        (do ((i start (1+ i)))
    	((or result (= i end))
    	 (let ((val (if result
    			(list result (- i 1))
    			#f)))
    	   (free-sample-reader reader)
    	   val))
          (set! result (func (next-sample reader))))))

snd4.scm has Snd-4 style scanning functions. For example:

    (define* (scan-all-chans proc #:optional (beg 0) end edpos)
      (catch 'done
    	 (lambda ()
    	   (apply for-each
    		  (lambda (snd chn)
    		    (let ((result (scan-chan proc beg end snd chn edpos)))
    		      (if result (throw 'done (append result (list snd chn))))))
    		  (all-chans)))
    	 (lambda args (cadr args))))

`scan-channel (func beg dur snd chn edpos)`
| scan-channel is the regularized version of scan-chan.
`search-procedure (snd)`
| the current global or sound-local (if snd is specified) search procedure.
`selected-channel (snd)`
| selected channel in snd (set to select channel).
`selected-sound ()`
| selected sound (index) (set to select sound).
`select-channel (chn)`
| select channel chn. [no-such-channel].
`select-sound (snd)`
| select sound snd (an index). [no-such-sound].
`set-samples (samp samps data snd chn trunc edname infile-chan edpos)`

set snd's channel chn's samples starting from sample samp for samps samples to the values in data. (If samp is beyond the end of the file, the file is first zero-padded to reach it). data can be a filename; Snd assumes any such file is temporary; it will be deleted when no longer needed. If trunc is #t and samp is 0, the sound is truncated (if necessary) to reflect the end of data. The form (set! (samples samp samps snd chn trunc edname infile-chan) data) can also be used.

`short-file-name (snd)`
| return the brief (no directory) form of snd's filename.
`show-axes (snd chn)`
| (default: show-all-axes) If show-all-axes, display x and y axes; if show-x-axis, just one (bottom) x axis is displayed, reducing screen clutter. The other choice is show-no-axes.
`show-marks (snd chn)`
| If #t, marks are displayed. This is the 'Show marks' View menu option.
`show-mix-waveforms (snd chn)`
| If #t (default = #t), mix displays the waveform of the sound being mixed.
`show-transform-peaks (snd chn)`
| If #t, transform peak information is included in the transform display (default: #f). (This is the 'peaks' button in the Transform options dialog).
`show-y-zero (snd chn)`
| If #t, the y=0 axis is displayed. This is the 'Show Y=0' View menu option.
`smooth-channel (beg dur snd chn edpos)`
| smooth-channel is the regularized version of smooth-sound.
`smooth-sound (beg num snd chn)`
| apply 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))))))))))

| For a fancier version, see fft-smoother in examp.scm. See also remove-clicks in examp.scm.
`sound? (snd)`
| #t if snd (an index) is active.
`soundfont-info (snd)`
| return a list of lists describing snd as a soundfont. Each inner list consists of the sound name, start point, loop start, and loop end. 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)))
                              (let ((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))))

`sound-loop-info (snd)`
| returns or sets loop points in header. In each case, 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).
`sounds ()`
| list of currently active sounds (id numbers). A common Snd trope is (map func (sounds)): (map maxamp (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 (1- (channels snd)) (1- i)))
                        ((< i 0))
                      (set! sndlist (cons snd sndlist))
                      (set! chnlist (cons i chnlist))))
                  (sounds))
        (list sndlist chnlist)))

    (apply map maxamp (all-chans))

`spectro-cutoff (snd chn)`
| The amount of the frequency domain to include in the spectrum display (default: 1.0). This number changes as you drag the frequency axis. This is the slider labelled '% of spectrum' in the View Orientation dialog.
`spectro-hop (snd chn)`
| The distance (pixels) moved between successive spectrogram traces (default = 4). This is the 'hop' slider in the Orientation dialog.
`spectro-start (snd chn)`
| The start point of the frequency domain to include in the spectrum display (default 0.0).
`spectro-x-angle (snd chn)`
| Default spectrogram x-axis viewing angle (default 90.0).
`spectro-x-scale (snd chn)`
| Default scaler (stretch) along the spectrogram x axis (default 1.0).
`spectro-y-angle (snd chn)`
| Same for y-axis (default 0.0).
`spectro-y-scale (snd chn)`
| Same for y-axis (default 1.0).
`spectro-z-angle (snd chn)`
| Same for z-axis (default -2.0).
`spectro-z-scale (snd chn)`
| Same for z-axis (default 0.1).
`squelch-update (snd chn)`
| are graphic updates squelched (turned off).
`srate (snd)`
| snd's sampling rate.
`src-channel (num-or-env-gen beg dur snd chn edpos)`
| sampling rate conversion using 'warped sinc interpolation'. The argument num-or-env-gen can be either a number or an envelope generator. This is the regularized version of src-sound.
`src-sound (num-or-env base snd chn edpos)`

sampling rate conversion using 'warped sinc interpolation'. The argument num-or-env can be either a number or an envelope. In the latter case, base sets the segment base (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.
num-or-env can also be a CLM env generator (its duration should be the same as the original sound).

`start-playing (chans srate background)`
| if a play-list is waiting, start it. chans defaults to 1, srate defaults to 44100, background defaults to #t.
`start-progress-report (snd)`
| see progress-report.
`stop-player (player)`
| remove player from play-list(see make-player). [no-such-player]
`stop-playing (snd)`
| if snd is playing, stop it [no-such-sound]. If no argument is given, stop all sounds (channels) in progress.
`swap-channels (snd1 chn1 snd2 chn2 beg dur)`
| Swap the indicated channels, between beg and beg+dur.
`sync (snd)`
| snd's 'sync' value (an integer, 0=off).
`time-graph-type (snd chn)`
| If time-graph-type is `graph-time-as-wavogram`, the time domain waveform is displayed as a 'wavogram'. The default is `graph-time-once`. See also wavo-hop and wavo-trace.
`transform-samples-size (snd chn)`
| returns either 0 if no transform, transform-graph-type if graph-transform-once, or (list full-size bins slices) if sonogram or spectrogram.
`transform-graph-type (snd chn)`
| choice of spectral display. The choices are (default) graph-transform-once, graph-transform-as-sonogram, and graph-transform-as-spectrogram.
`transform-normalization (snd chn)`

Transform normalization choice (default: normalize-transform-by-channel) If normalize-transform-by-channel or normalize-transform-by-sound, spectral data is normalized to 1.0 before display. If dont-normalize-transform, you get the raw data values, which can reflect amplitude changes -- Snd tries to choose a y axis limit that makes successive displays move smoothly. The other choice is normalize-transform-globally (i.e. across all sounds).

`transform-sample (bin slice snd chn)`
| return the current value of the transform (if any) in bin and (if a sonogram or spectrogram) slice in snd's channel chn. Use update-transform to make sure the transform has run to completion. [no-such-sample].
`transform-samples (snd chn)`
| return the transform data currently in snd's channel chn. Use update-transform to make sure the transform has run to completion.
`transform-samples->vct (snd chn v)`
| return vct struct with the transform data currently in snd's channel chn. If v (a vct) is provided, it is filled, rather than creating a new vct. Use update-transform to make sure the transform has run to completion.
`transform-size (snd chn)`
| FFT size (default = 256).
`transform-type (snd chn)`

The spectrum transform type (default: fourier-transform).

    fourier-transform  wavelet-transform   hankel-transform    chebyshev-transform
    autocorrelation    walsh-transform     hadamard-transform  cepstrum     haar-transform

`undo (edits snd chn)`
| undo edits edits (default 1) in snd's channel chn.
`update-lisp-graph (snd chn)`
| redisplay chn's lisp graph.
`update-sound (snd)`
| update snd (re-reads data from disk, flushing any pending edits). In some cases (primarily involving a change in the number of channels), update-sound can change the index of the sound referred to by 'snd'.
`update-time-graph (snd chn)`
| redisplay chn's graph(s).
`update-transform (snd chn)`
| recalculate chn's fft, forcing it to completion.
`view-sound (filename)`
| open filename read-only. [no-such-file].
`verbose-cursor (snd chn)`
| If #t, the cursor's position and other information is constantly displayed in the minibuffer. This is the View:Verbose cursor option (default: #f).
`wavelet-type (snd chn)`
| If transform-type is wavelet-transform, wavelet-type selects which wavelet is used. The list of available wavelets is in the Transform Dialog. There are currently 20 choices, so this variable goes from 0 to 19 (default: 0).
`wavo-hop (snd chn)`
| This sets the distance upward between wavogram traces; that is, the smaller this number, the more traces can be displayed (default: 3). See time-graph-type.
`wavo-trace (snd chn)`
| This sets the length (samples) of each wavogram trace (default: 64). See time-graph-type.
`write-peak-env-info-file (snd chn filename)`
| writes the current peak-env amp info of the given channel to filename.
`x-axis-style (snd chn)`
| The x axis labelling of the time domain waveform can be in seconds (x-axis-in-seconds), in samples (x-axis-in-samples), expressed as a percentage of the overall duration (x-axis-as-percentage, useful in envelope definitions), or as a beat number (x-axis-in-beats). This is the View menu 'X-axis units' option. (default: x-axis-in-seconds).
`x-bounds (snd chn)`
| return (list x0 x1) -- current x axis time domain bounds in seconds. `(set! (x-bounds) (list 0.0 (/ (frames) (srate))))` shows the full sound.
`x->position (x snd chn axis)`
| returns the graph position that corresponds to the X axis value x. [no-such-axis].
`x-position-slider (snd chn)`
| value of x axis position slider.
`x-zoom-slider (snd chn)`
| value of x axis zoom slider.
`y-bounds (snd chn)`
| return (list y0 y1) -- current y axis bounds. To set the bounds to reflect the channel's maxamp, use (set! (y-bounds) '()). To set all channels at once using the selected sound's maxamp:

    (let ((maxval (apply max (maxamp #f #t))))
      (do ((i 0 (1+ i)))
          ((= i (chans)))
        (set! (y-bounds #f i) (list (- maxval) maxval))))

| Or to set each channel to its own maxamp:

    (do ((i 0 (1+ i)))
        ((= i (chans)))
      (let ((maxval (maxamp #f i)))
        (set! (y-bounds #f i) (list (- maxval) maxval))))

`y->position (y snd chn axis)`
| returns the graph position that corresponds to the Y axis value y. [no-such-axis].
`y-position-slider (snd chn)`
| value of y axis position slider.
`y-zoom-slider (snd chn)`
| value of y axis zoom slider.
`zero-pad (snd chn)`
| fft zero pad size as a multiple of the fft size; `(set! (zero-pad) 1)` gives you half data, half zeros (default: 0) (the data length is determined by the nominal transform-size). Zero padding causes sinc-interpolation of the fft points, making the display smoother.

    (bind-key (char->integer #\p) 0
      (lambda ()
        (set! (zero-pad) (+ (zero-pad) 1))
        (update-transform)))

    (bind-key (char->integer #\m) 0
      (lambda ()
        (set! (zero-pad) (- (zero-pad) 1))
        (update-transform)))

#### the control panel

The control panel normally processes samples as follows: if the sampling rate conversion is on (the 'Speed' control is not 1.0), it applies srate conversion to the incoming sample; the next stage is the expansion function, if the 'Expand' toggle button is set; this value is passed next to the Contrast function, if it is running, and then the result is scaled by the Amp slider's value. The filter is run next, if it's on, and finally the sample is scaled by the reverb slider and passed to the reverb, if any, which adds its result to the sample; the final result is sent to the speakers. Besides the various user interface controls, and variables such as expand-control-hop, you can also change this sequence by replacing the contrast or reverb functions with your own. set-contrast-control-procedure replaces the contrast-enhancement function; set-reverb-control-procedures replaces the reverb. The control panel procedures are:

`amp-control (snd chn)`
| current amp (control panel slider) value. It is possible to use these controls (in "real-time") in your own functions. See amprt in examp.scm for a simple example. As an experiment, I added the optional chn argument; if it is specified, the channel's local amp-control value is set instead of the sound's. This affects the apply button (apply-controls) and playback.
`apply-controls (snd target beg dur)`
| equivalent to pushing snd's 'apply' button. target can be 0=sound, 1=channel, 2=selection. beg sets where (in samples) the apply starts; `(apply-controls 0 0 (mark-sample m))` starts from the given mark. dur if given sets how many (original) samples to run the apply process. apply-controls can be used in conjunction with the various control panel variables:

    (define expnd
      (lambda (amt)
        (set! (expand-control?) #t)
        (set! (expand-control) amt)
        (apply-controls)))

`contrast-control (snd)`
| current contrast (control panel slider) value.
`contrast-control-amp (snd)`
| snd's contrast-control-amp (control panel variable).
`contrast-control? (snd)`
| #t if snd has contrast turned on (control panel)
`expand-control (snd)`
| current expansion amount (control panel).
`expand-control-hop (snd)`
| snd's expansion hop amount (seconds).
`expand-control-length (snd)`
| snd's expansion segment length (seconds).
`expand-control-ramp (snd)`
| snd's expansion ramp amount (between 0 and .5). This affects the smoothness of the grain overlaps -- .001 gives a rattling effect.
`expand-control? (snd)`
| #t if snd's expand button is on.
`filter-control-in-dB (snd)`
| snd's filter dB button state (control panel).
`filter-control-env (snd)`
| snd's filter envelope (control panel).
`filter-control-order (snd)`
| snd's filter order (control panel).
`filter-control? (snd)`
| #t if snd is filtering (control panel filter button).
`reset-controls (snd)`
| clears the control panel.
`restore-controls (snd)`
| same as pushing the control panel 'r' button.
`reverb-control-decay (snd)`
| The length (seconds) of the reverberation after the sound has finished (default: 1.0).
`reverb-control-feedback (snd)`
| snd's reverb feedback coefficient.
`reverb-control-length (snd)`
| reverb delay line length scaler (control panel).
`reverb-control-lowpass (snd)`
| reverb low pass filter coefficient.
`reverb-control-scale (snd)`
| reverb amount (control panel).
`reverb-control? (snd)`
| #t if snd's reverb button is on.
`save-controls (snd)`
| same as pushing the control panel 's' button.
`set-contrast-control-procedure (func)`
| set contrast procedure to func, a function of two arguments, the current sample and the current 'contrast' slider setting, returning a new sample, the result of the control panel contrast function; func replaces the built-in contrast procedure. The built-in default (mus-contrast-enhancement) is snd-contrast.
`set-reverb-control-procedures (reverb make-reverb free-reverb)`
| This function replaces the built-in Snd reverb with your own.

    (define delay-line #f)
    (define delay-time 0.5)

    (set-reverb-control-procedures
      (lambda (ptr invals outvals)
        (vct-set! outvals 0
          (delay delay-line
                 (+ (* .75 (tap delay-line))
                    (vct-ref invals 0))))
        outvals)
      (lambda (snd chans)
        (set! delay-line
          (make-delay (inexact->exact (* (srate snd) delay-time))))
        delay-line)
      (lambda (ptr)
        ptr))

The three arguments are the reverb, the function that makes or initializes the reverb, and the function to free the reverb. The built-in defaults are snd-nrev, make-snd-nrev, and free-snd-nrev. Another choice is Jezar Wakefield's Freeverb: snd-freeverb, etc. The make-reverb function takes two arguments, the current sound's index, and the number of output channels active; it returns an opaque object that is subsequently passed to the reverb and free-reverb functions The reverb function takes 3 arguments, the thing returned by make-reverb, the current reverb input samples (a vct object), and the output vct object which it should fill with the current reverb output contribution.
I may change this to take a list of three functions, rather than the three functions themselves; as it is, there's no generalized set! for reverb-procedures.

`show-controls (snd)`
| if #t, opens snd's control panel, else closes it.
`speed-control (snd)`
| current speed (control panel).
`speed-control-style (snd)`
| In the control panel, the 'speed' control can be interpreted as a float, (speed-control-as-float, the default), as a just-intonation ratio of relatively small integers (speed-control-as-ratio) or as a step in a microtonal scale (speed-control-as-semitone).
`speed-control-tones (snd)`
| The number of tones per octave in the speed-control-as-semitone speed style (default: 12).

### Edit Lists

An edit list describes the edit history of a channel. When, for example, you type C-d, nothing actually happens to any data, despite the fact that the graph no longer shows that sample, it's gone when you play the channel, and so on. What actually happens is that a descriptor is appended to the edit history of that channel saying "sample n was deleted". Undo and redo move around in this list (they simply move the pointer to the current edit history position); all the positions are accessible just like the current one, and are exposed in many functions described above as the pos argument. The edit list functions are:

`as-one-edit (func origin)`
| apply func (a function of no arguments), treating it as one edit (in all channels) in the edit history mechanism.

    (as-one-edit
      (lambda ()
        (set! (sample 100) .1)
        (set! (sample 200) .2)))

`display-edits (snd chn)`
| returns the current edit list contents (as a string).
`edit-fragment (num snd chn)`
| return a list similar to that displayed in the edit history window giving the origin of the specified edit, its type (delete, insert, etc), its begin sample (given the current edit tree), and the number of samples affected. If num is omitted, return the last (currently active) edit [no-such-edit].
`edit-position (snd chn)`
| current position in edit history list (can be set).
`edits (snd chn)`
| return a list with number of undo-able edits and redo-able edits.
`edit-tree (snd chn pos)`
| return list of lists completely describing current edit list. Each inner list has the form '(global-position data-number local-position local-end scaler). If data-number is -2, it marks the end of the list. The following function uses this information to highlight the changed portions of a given sound.

    (define (show-original snd chn)
      ;; draw a bar above unchanged portions of a sound
      (define (check-fragment tree ls rs)
        (let* ((fragment (car tree))       ; current edit list fragment
               (pos (car fragment))        ; its position in sound
               (dat (cadr fragment))
               (scl (list-ref fragment 4)))
          (if (and (= dat 0)               ; original sound
                   (= scl 1.0))            ; unscaled
              (let ((nxtpos (car (cadr tree))))
                (if (and (<= pos rs)
                         (>= nxtpos ls))   ; fragment is at least partially visible
                    (let ((x0pos (x->position (/ (max ls pos) (srate))))
                          (x1pos (x->position (/ (min rs nxtpos) (srate)))))
                      (fill-rectangle x0pos 2 (- x1pos x0pos) 5)))))
          (if (and (cdr tree)              ; go to next fragment
                   (not (= (cadr (car tree)) -2)))
              (check-fragment (cdr tree) ls rs))))
       (check-fragment (edit-tree snd chn)
                       (left-sample snd chn)
                       (right-sample snd chn)))
    (add-hook! after-graph-hook show-original)

`revert-sound (snd)`
| revert snd to saved state (undo all edits).
`save-edit-history (filename snd chn)`

save current edit list(s) in filename. If chn is omitted, all snd's channels are saved; if snd is omitted, all edit list are saved. If the underlying files are not subsequently changed, you can load this file to restore the current edit list state. Returns #t if successful (file opened ok); if something went wrong [cannot-save]. The following function makes an exact copy of the state (edit lists and all) of the given sound, providing a way to "fork" an edit path (geez, what jargon! The idea here is to copy the complete edit state into a new sound so that two or more edit sequences can be compared).

    (define sfile 0)

    (define* (clone-sound-as new-name #:optional snd)
      (let* ((tmpf (snd-tempnam))
    	 (scm (string-append (substring tmpf 0 (- (string-length tmpf) 3)) "scm"))
    	 (oldsnd (or snd (selected-sound))))
        (if (not (string? (save-dir))) (set! (save-dir) "/tmp"))
        (save-edit-history scm oldsnd)
        (copy-file (file-name oldsnd) new-name)
        (set! sfile (open-sound new-name))
        (load scm)
        (delete-file scm)
        sfile))

`redo (edits snd chn)`
| redo edits edits (default is 1) in snd's channel chn.
`undo (edits snd chn)`
| undo edits edits (default 1) in snd's channel chn.

It is sometimes more convenient to edit the edit history lists directly, than to run Snd and invoke the "Save State" menu option. To save a particular sound's or channel's edit list(s), use the function save-edit-history. These lists are simply Scheme programs, just like anything else discussed in this document. You could even write them from scratch. Say we want to make a stereo file that consists of four mono files mixed at various points; we know where they should go, and we have religious objections to using a graphical user interface. So we create myfile.scm, and put in it something like:

    (let ((myfile (new-sound "mysound.snd" mus-aifc-sound-file mus-bshort 44100 2 "this is my sound")))
    	;; this is equivalent to the New file menu option
      (mix "oboe.snd" 0 0 myfile 0)
      ;; this mixes in the mono file oboe.snd at sample 0 in channel 0
      ;; use (mix "oboe.snd" 0 0 myfile 0 #f) to forego the editable mix
      (mix "pistol.snd" 0 0 myfile 1)
      ;; now pistol.snd is at sample 0 in channel 1
      (mix "fyow.snd" 10000 0 myfile 0)
      ;; add in fyow.snd at sample 10000 in the first channel
      (mix "cardinal.snd" 20000 0 myfile 1)
      ;; etc
      )

Now fire up Snd: `snd -l myfile.scm` and voila! Files like this can contain any arbitrary Scheme code, calling anything in Snd or anywhere else for that matter; you basically have a CLM-like notelist reader to describe sound file edits. Similarly, when you save Snd's state (via the Save State menu option or by calling the function save-state), the result is a Scheme program that can be edited just like any other such text.

Many editing operations within Snd actually only affect the current edit lists. For example, if you delete a portion of a sound, the only thing that happens is that the edit list of that sound is updated to reflect the jump over the deleted portion. Similarly, all scaling operations (normalization etc), operations resulting in zeros, and simple cases of channel swaps are actually only editing the edit list. This means that ideally such operations are instantaneous and take no disk space no matter how large the sound being edited (in other cases we have to save at least the changed portion of the sound). I'm toying with extending this part of Snd to more complex operations.

### Transforms

Except for add-transform, the transform functions and variables have been treated above, so this is just a list of them with cross-references.

`add-transform (name xlabel lo hi transform)`

add-transform adds a transform to the transform tables. name is the name it will be listed by in the transform dialog. xlabel is the x axis label of the resultant graph. lo and hi set which portion of the returned data is relevant in graphing (normally 0.0 to 1.0). proc is a function of two arguments, the length of the desired transform, and a sample-reader that can be used to get the current data. Do not free the sample-reader! The function should return a vct object containing the transform data. add-transform returns the new transform's transform-type. Here's an example that displays a histogram of the current values in 16 bins:

    (add-transform "histogram" "bins" 0.0 1.0
      (lambda (len fd)
        (let ((v (make-vct len))
              (steps (/ len 16))
              (step (/ 1.0 len)))
          (do ((i 0 (1+ i)))
              ((= i len) v)
            (let* ((val (next-sample fd))
                   (bin (inexact->exact (* (abs val) 16.0))))
              (do ((j 0 (1+ j)))
                  ((= j steps))
                (vct-set! v
                          (+ j bin)
                          (+ step (vct-ref v (+ j bin))))))))))

| And an example that lets us see the result of filtering the waveform:

    (add-transform "low-pass" "filtered" 0.0 1.0
      (lambda (len fd)
        (let ((v (make-vct len))
              (flt (make-fir-filter :order 80
                                    :xcoeffs (let ((v1 (make-vct 80)))
                                               (vct-fill! v1 .0125)
                                               v1))))
         (do ((i 0 (1+ i)))
             ((= i len) v)
          (vct-set! v i (fir-filter flt (next-sample fd)))))))

`autocorrelate (data)`
| return (in place) autocorrelation of data
`convolve-arrays (rl1 rl2)`
| convolve vectors rl1 with rl2. Result returned in rl1. rl1 should be large enough to hold the full convolution result. As a special dispensation for forgetful users, if rl1 is a file name and rl2 is not a vector, convolve-with is called instead.
`fft (rl im sgn)`
| perform an FFT on rl and im (the real and imaginary parts of the input data. sgn is 1 for an FFT, -1 for an inverse FFT; (default 1).
`fht (vobj)`

perform Hartley transform on vct vobj whose length must be a power of 4. This is a fast version of the FFT (it's about 4 to 6 times faster than the fft, but can be applied only to real data; it is its own inverse). The following function reads data from the left window edge, calls the Hartley transform (using a "rectangular window"), turns that into a magnitude spectrum, and graphs the result (which is the same as the fft-generated magnitude spectrum before any normalization etc).

    (define (hgrf snd chn y0 y1)
      (let* ((size (transform-size snd chn))
             (hv0 (samples->vct (left-sample snd chn) size))
             (hv1 (make-vct (/ size 2))))
        (fht hv0)
        (do ((i 1 (1+ i)))
            ((= i (/ size 2)))
          (vct-set! hv1 i
            (* (sqrt (+ (* (vct-ref hv0 i)
                           (vct-ref hv0 i))
                        (* (vct-ref hv0 (- size i))
                           (vct-ref hv0 (- size i)))))
               (/ (sqrt 2.0) size))))
        (graph hv1)))

    (add-hook! graph-hook hgrf)

| The explicit (slow) form of this transform is dht in dsp.scm.

Other related variables and functions:

graph-transform? | show-transform-peaks | transform-sample
fft-window-beta | show-selection-transform | transform-samples
transform-hook | spectro-cutoff | transform-samples->vct
fft-log-frequency | spectro-hop | transform-samples-size
fft-log-magnitude | spectro-start | transform-type
transform-size | spectro-x-angle | update-transform
transform-graph-type | spectro-x-scale | transform-normalization
fft-window | spectro-y-angle | zero-pad
max-transform-peaks | spectro-y-scale | wavelet-type
min-dB | spectro-z-angle | spectro-z-scale

### Dialogs

Many aspects of the various dialogs can be customized. The following is organized by dialog, or was meant to be originally.

`dismiss-all-dialogs ()` | deactivate all dialogs.
`just-sounds ()` | reflects the just-sounds button (if any). See also just-sounds-hook.
`file-dialog ()` | create the list of current and previous files (not the file browser), if necessary, activate it, and return the dialog widget.
`mix-file-dialog (managed)` | create the File:Mix file selection dialog, if necessary, and make it visible if managed.
`open-file-dialog (managed)` | create the File:Open file selection dialog, if necessary, and make it visible if managed.
`previous-files-sort ()` | Sort choice in files dialog (0=unsorted, 1=name, 2=date, 3=size, 4=entry, 5=user procedure).
`previous-files-sort-procedure ()` | Procedure used to sort files in the files dialog. It takes one argument, the list of files, and should return that list, sorted as it pleases. For example, the following sorts the list by decreasing max amp:

    (set! (previous-files-sort-procedure)
      (lambda (lst)
        (sort lst
    	  (lambda (a b)
    	    (define (mxall mxcur mxlst)
    	      (if (null? mxlst)
    		  mxcur
    		  (mxall (max mxcur (cadr mxlst)) (cddr mxlst))))
    	    (let ((mxa (mus-sound-maxamp a))
    		  (mxb (mus-sound-maxamp b)))
    	      (or (null? mxb)
    		  (and (not (null? mxa))
    		       (> (mxall 0.0 mxa)
    			  (mxall 0.0 mxb)))))))))

`edit-header-dialog (snd)` | fire up Edit Header dialog on snd.
`edit-save-as-dialog ()` | fire up Edit Save-as dialog (to save the current selection).
`enved-active-env ()` | Envelope editor current envelope (list).
`enved-base ()` | Envelope editor exponential base value (1.0)
`enved-clip? ()` | Envelope editor 'clip' button (restricts mouse motion) (#f)
`enved-in-dB ()` | Envelope editor 'dB' button (#f)
`enved-dialog ()` | create the Envelope editor dialog, if necessary, activate it, and return the dialog widget. See also enved-hook.
`enved-exp? ()` | Envelope editor 'exp' and 'lin' buttons (type of connecting segments) (#f)
`enved-filter ()` | The type of the Envelope editor's filter. (default #t: FIR, #f is FFT). To get the fft display in the envelope editor as the default:

    (define (enved-fft)
      (set! (enved-filter) #f)
      (set! (enved-wave?) #t)
      (set! (enved-target) enved-spectrum)
      (add-hook! after-open-hook (lambda (snd)
    			       (set! (transform-size snd)
    				     (expt 2 (ceiling (/ (log (frames snd))
    							 (log 2.0))))))))

`enved-filter-order ()` | The order of the Envelope editor's FIR filter. (default is 40)
`enved-power ()` | Envelope editor base scale range (9.0^power). (3.0)
`enved-selected-env ()` | Envelope editor selected envelope (list).
`enved-target ()` | Determines how the envelope is applied to the current data This chooses one of the 'amp', 'flt', and 'src' buttons in the Envelope editor. The other (named constant) choices are enved-srate and enved-spectrum. defaults to enved-amplitude
`enved-waveform-color ()` | color of waveform displayed in envelope editor. (default is blue).
`enved-wave? ()` | Envelope editor 'wave' button The wave shown is the time domain display, even when filtering.
`file-save-as-dialog ()` | fire up File Save-as dialog (to save the currently selected sound).
`help-dialog (subject help-string)`
| start the help dialog with title subject and body help, returning the dialog widget. `(help-dialog "xyzzy" "are we having fUn?")`
`help-text-font ()` | help dialog text font
`html-dir ()` | If an HTML widget is in use, the directory to search for documentation.
`listener-color ()` | background color of lisp listener. `(set! (listener-color) (make-color 0 0 0))` is good too.
`listener-font ()` | listener font.
`listener-prompt ()` | lisp listener prompt (defaults to ">").
`listener-text-color ()` | text color in lisp listener.

    (define (red-alert msg)
      ;; this only works in the gtk version (in Motif all existing text changes color)
      (let ((old-color (listener-text-color)))
        (set! (listener-text-color) (make-color 1 0 0))
        (snd-print msg)
        (set! (listener-text-color) old-color)))

`mix-panel ()` | create the Mix Panel Dialog, if necessary, activate it, and return the dialog widget.
`orientation-dialog ()` | Create the Orientation dialog, if necessary, activate it, and return the dialog widget.
`spectro-cutoff ()` | The amount of the frequency domain to include in the spectrum display. This number changes as you drag the frequency axis, for example. This is the slider labelled '% of spectrum' in the View Orientation dialog. default: 1.0.
`spectro-hop ()` | The distance (pixels) moved between successive spectrogram traces. This is the slider labelled 'hop' in the Orientation dialog. default: 4.
`spectro-start ()` | The start point of the frequency domain to include in the spectrum display. default: 0.0
`spectro-x-angle ()` | Default spectrogram x-axis viewing angle. default: 90.0
`spectro-x-scale ()` | Default scaler (stretch) along the spectrogram x axis. (default: 1.0)
`spectro-y-angle ()` | Same for y-axis. default: 0.0
`spectro-y-scale ()` | Same for y-axis. default: 1.0
`spectro-z-angle ()` | Same for z-axis. default: -2.0
`spectro-z-scale ()` | Same for z-axis. default: 0.1
`print-length ()` | number of elements of lists and vectors that are printed (default is 12).
`recorder-autoload ()` | The 'autoload' button in the recorder dialog. (#f)
`recorder-buffer-size ()` | The size of the recorder input buffer (there's a trade-off between responsiveness and clicks in some cases). (4096)
`recorder-dialog ()` | fire up recorder window.
`recorder-file ()` | Default recorder output file name.
`recorder-gain (gain)` | recorder input (soundcard-audio) gain gain.
`recorder-in-amp (in out)` | recorder input channel in to output channel out amplitude.
`recorder-in-device ()` | Input device for recorder. `(set! (recorder-in-device) mus-audio-line-in)`
`recorder-in-format ()` | Incoming data format for the recorder. default 16-bit linear.
`recorder-max-duration ()` | Recorder max output file length.
`recorder-out-amp (out)` | recorder file output channel out amplitude.
`recorder-out-chans ()` | Recorder output file channels. (default: 2)
`recorder-out-format ()` | same as recorder-in-format
`recorder-srate ()` | Recorder sampling rate.
`recorder-trigger ()` | Recorder auto-trigger value.
`show-listener ()` | if set to #t open the lisp listener pane, else close it. For backwards compatibility, if called outside set!, it opens the pane.
`save-listener (filename)` | save listener text in filename.
`vu-font ()` | The "vu-" variables refer to the VU meters in the recorder. vu-font is the font used to label the meters. It is normally "courier".
`vu-font-size ()` | recorder VU meter label font size. (1.0)
`vu-size ()` | overall size of the recorder VU meters. (1.0)
`region-dialog ()` | fire up region browser (a no-op if no regions).
`transform-dialog (managed)` | create the Transform Dialog, if necessary, activate it (if managed is #t, the default), and return the dialog widget. (This is Option menu's Transform Options choice).
`yes-or-no? (ques)` | modal error dialog, #t if user clicks "yes", otherwise #f.

In normal use, this is a very annoying thing to call because it forces everything to stop until the poor user clicks a button; in Snd, it's used for questions like "destroy disk?". While developing code, however, yes-or-no? can be a very handy way to stop and restart a computation. For example, say we're trying to get remove-clicks in examp.scm to work, and can't see why a click is being missed. We can use yes-or-no? to step through the samples one at a time, breaking out of the computation at any time (the ellipses here mark code omissions -- see the original for details):

        (call-with-current-continuation
         (lambda (return)
           (do ((ctr loc (1+ ctr)))
    	   ((= ctr len) #f)
    	...
    	 (let ((local-max (max .1 (vct-peak samps))))
    	   (snd-print (format #f "~A " local-max))
    	   (if (yes-or-no? "continue?")
    	       (if (and (> (abs (- samp0 samp1)) local-max)
                            ...
    		   (return (1- ctr)))
    	       (return #f)
                   ...

But this is kinda dumb; we really should use a continuation here:

        (call-with-current-continuation
         (lambda (return)
           (do ((ctr loc (1+ ctr)))
    	   ((= ctr len) #f)
             ...
    	 (let ((local-max (max .1 (vct-peak samps))))
    	   (call-with-current-continuation
    	    (lambda (go-on)
    	      (snd-print (format #f "~A: ~A (~A) " ctr local-max samp2))
    	      (return go-on)
    	      (if (and (> (abs (- samp0 samp1)) local-max)
                      ...
    		  (return (1- ctr))

Now save the continuation after the first call, and whenever you want to pick up where you last left off, call it as a function of no arguments. This method leaves all the rest of Snd operational (in particular the listener), whereas yes-or-no? tries to freeze everything until you click its button.

The main menus can be extended, and new menus added with the following functions:

`add-to-main-menu (menu-label update-callback)`

add new top-level menu named menu-label, return menu index. update-callback can be a procedure of no arguments that will be called each time the menu is displayed. (See effects.scm for an example).

      >(add-to-main-menu "Tools")
      5

`add-to-menu (top-menu menu-label callback position)`

add menu menu-label to top-level menu whose index is top-menu with the callback function callback. The built-in Snd menus are numbered from 0 ('File') to 5 ('Popup') ('Help' is 4). If label and callback are #f, a separator is added to the menu. position sets the position of the new menu option; it defaults to the end of the menu. See effects.scm for many examples. [no-such-menu]

      (add-to-menu 1 "Stop Playing"
        (lambda () (stop-playing)))

      (add-to-menu 5 "Reduce height"
        (lambda () (set! (window-height) (/ (window-height) 2))))

As a slightly more elaborate example, the following adds a Delete option to the File menu:

    (add-to-menu 0 "Delete" ; add Delete option to File menu
    	     (lambda ()
    	       ;; close current sound and delete it (after requesting confirmation)
    	       (if (>= (selected-sound) 0)
    		   (let ((filename (file-name)))
    		     (close-sound)
    		     (if (yes-or-no? (format #f "delete ~S?" filename))
    			 (delete-file filename)))))
    	     8) ; place after File:New

`change-menu-label (top-menu old-label new-label)`
`remove-from-menu (top-menu menu-label)`
| remove menu menu-label from the top top-level menu whose index is top-menu.
`menu-sensitive (top-menu label)`

In addition, the hook menu-hook is checked each time a menu item is selected; its entries should be functions of two arguments, the top menu name and the option selected (both as strings), and should return #f if it wants to override the default menu action.

      (add-hook! menu-hook
        (lambda (name option)
          (if (and (string=? name "File")
                   (string=? option "Exit"))
            (begin
              (snd-print "no exit!")
    	  #f)
            #t))) ; #t to make sure other menu items remain active

I may change these menu handlers to use the menu-widgets list and more general functions; let me know what you'd like to be able to do!

### Errors

When something goes awry, the various functions can "throw" an error (a symbol) which is normally "caught" by the default error handler (this is a kind of "goto" but without the embarrassment); it prints out some message, and in some cases appends a stack trace. So, as a simple example, selection-position throws 'no-active-selection if there isn't a selection. In the default case, you get behavior like this:

    >(selection-position)
    selection-position: no-active-selection
    >asdf
    Unbound variable: asdf

But there are cases where you'd rather handle an error (or all errors) specially. In the case of 'no-active-selection, we set up our own handler for that as follows:

    >(catch 'no-active-selection
           (lambda ()
             (+ 1 (selection-position)))
           (lambda (tag val) 0))
    0

Here we've "caught" 'no-active-selection (if it occurs within the first "thunk's" body), and return 0 if it occurs; otherwise we return `(+ 1 (selection-position))`. Scheme (Guile) has a number of errors such as 'out-of-range, 'wrong-type-arg, 'numerical-overflow, etc. The Snd-specific errors are:

    'no-such-channel 'no-such-sound  'no-such-mark       'no-such-mix
    'no-such-menu    'no-such-file   'no-such-region     'no-such-sample
    'no-such-edit    'cannot-save    'impossible-bounds  'no-active-selection
    'no-such-widget  'mus-error      'no-such-track      'no-such-envelope
    'cannot-print    'no-such-axis   'no-such-player     'bad-arity
    'no-such-color   'no-such-widget 'no-such-plugin     'no-such-graphics-context

bad-arity is jargon indicating that a procedure takes the wrong number of arguments. The symbol #t stands for all errors in this case, so we can run rough-shod over any error with:

    (defmacro without-errors (func)
      `(catch #t
    	  (lambda ()
    	    ,func)
    	  (lambda args (car args))))

You can use these errors in your code, if you like. The following throws the error 'no-such-file:

    (define look-for-file
      (lambda (file)
        (or (file-exists? file)
    	(throw 'no-such-file (list "look-for-file" file)))))

There are a variety of debugging aids supplied by Guile, including a good backtrace facility. You can have a backtrace displayed automatically in the error handler by setting show-backtrace to #t. To see what files are being loaded, set the variable %load-verbosely to #t.

### Constants

Sndlib (see sndlib.html for a complete list):

      mus-next mus-aifc mus-riff mus-nist mus-raw mus-ircam mus-aiff
      mus-bicsf mus-soundfont mus-voc mus-svx

      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

      mus-out-format

Time domain graph type:

      graph-time-once  graph-time-as-wavogram

Transform graph type (the Transform Options Display choice):

      graph-transform-once  graph-transform-as-sonogram  graph-transform-as-spectrogram

Transform type:

      fourier-transform  wavelet-transform   hankel-transform    chebyshev-transform
      autocorrelation    walsh-transform     hadamard-transform  cepstrum   haar-transform

Transform normalization:

      dont-normalize-transform   normalize-transform-by-channel normalize-transform-by-sound  normalize-transform-globally

FFT Window type:

      rectangular-window hann(ing)-window   welch-window      parzen-window
      bartlett-window    hamming-window     blackman2-window  blackman3-window
      blackman4-window   exponential-window riemann-window    kaiser-window
      cauchy-window      poisson-window     gaussian-window   tukey-window
      dolph-chebyshev-window (if GSL is loaded)

Zoom Focus style:

      zoom-focus-left    zoom-focus-right   zoom-focus-active zoom-focus-middle

X-axis Label:

      x-axis-in-seconds  x-axis-in-samples  x-axis-as-percentage  x-axis-in-beats

Speed Control style:

      speed-control-as-float     speed-control-as-ratio     speed-control-as-semitone

Channel Combination style;

      channels-separate  channels-combined  channels-superimposed

Envelope Editor target:

      enved-amplitude      enved-spectrum       enved-srate

Graph Line style:

      graph-lines        graph-dots         graph-filled      graph-lollipops
      graph-dots-and-lines

Key binding cursor action:

      cursor-in-view     cursor-on-left     cursor-on-right   cursor-in-middle
      cursor-update-display cursor-no-action keyboard-no-action

Cursor style:

      cursor-cross   cursor-line

Axis placement choice:

      show-all-axes  show-no-axes  show-x-axis

Graph id:

      time-graph     transform-graph     lisp-graph

### Miscellaneous functions

These functions don't seem to fit anywhere else:

`abort ()`
| exit Snd via "abort", presumably to fall into the C debugger (gdb).
`add-sound-file-extension (ext)`
| add ext to the list of (case sensitive) sound file extensions. The initial list is ("snd" "aiff" "aif" "wav" "au" "aifc" "voc" "wve" "WAV" "sf2")
`bind-key (key state func extended)`

Cause key (an integer) with modifiers state (and preceding C-x if extended) to evaluate code.
`(bind-key (char->integer #\a) 4 (lambda () (snd-print \"hi\") cursor-in-view))`
`(bind-key (char->integer #\p) 0 (lambda () cursor-on-left)) ;now (cursor) == (left-sample)`
The modifier state is a combination of shift: 1, control: 4, meta: 8, so this call causes C-a to print "hi" in the lisp listener. The value returned should be one of the cursor choices telling Snd what action (if any) to take after evaluating code. Possible return values are:

      cursor-in-view     cursor-on-left      cursor-on-right    cursor-in-middle
      cursor-update-display cursor-no-action keyboard-no-action

| The function bound to a key can take either no arguments (as above), or one argument. In the latter case, the argument is the count (the C-u number prefixed to the keyboard command) defaulting to 1 if no prefix is typed. For example, there used to be a "line-size" variable setting how how many samples to jump for the C-p and C-n keys. We can implement the same idea:

    (define line-size 128)
    (bind-key (char->integer #\n) 4 (lambda (arg) (forward-sample (* line-size arg))))
    (bind-key (char->integer #\p) 4 (lambda (arg) (backward-sample (* line-size arg))))

| We can use bind-key to turn the keyboard into a sort of extended piano:

    (bind-key (char->integer #\o) 0
      (lambda ()
        (play "oboe.snd")
        keyboard-no-action))

    (bind-key (char->integer #\p) 0
      (lambda ()
        (play "pistol.snd")
        keyboard-no-action))

| Now each time we hit "o", "oboe.snd" plays, etc. Or say we want to move forward two samples in the graph each time we type "l":

    (bind-key (char->integer #\l) 0
      (lambda ()
        (set! (left-sample 0 0) (+ 2 (left-sample 0 0)))
        cursor-update-display))

| Or, more useful perhaps, have C-c set the cursor at a particular sample:

    (bind-key (char->integer #\c) 4
      (lambda (arg)
        (set! (cursor) arg)
        cursor-in-middle))

The key codes can usually be found in the X header file X11R6/include/X11/keysymdef.h The "Page Up" key is given as 0xFF55, which in Scheme would be #xff55 (65365).

      (bind-key #xff55 0 (lambda () (snd-print "Page up") keyboard-no-action))

Similarly, the End key if #xFF57, so we could bind it to cause the full sound to be displayed:

      (bind-key #xFF57 0 (lambda () (set! (x-bounds) (list 0.0 (/ (frames) (srate))))))

| The emacs-style line-oriented commands C-p, C-n, and C-k aren't very useful in Snd, since there's no reason 128 samples would consititute the audio analog of a line of text. In the next example, we'll rebind them to treat same-sense zero-crossings as line markers:

    (define (find-zero forwards)
      (let* ((loc (cursor))
    	 (twice #f)
    	 (dir (if forwards 1 -1))
    	 (reader (make-sample-reader loc #f #f dir))
    	 (val (if forwards (next-sample reader) (previous-sample reader))))
        (while (not (or (c-g?)
    		    (and forwards (sample-reader-at-end? reader))
    		    (and (not forwards) (= loc 0))))
          (let ((newval (if forwards (next-sample reader) (previous-sample reader))))
    	(set! loc (+ loc dir))
    	(if (or (and (>= val 0.0) (< newval 0.0))
    		(and (< val 0.0) (>= newval 0.0)))
    	    (if twice
    		(break loc)
    		(begin
    		  (set! val newval)
    		  (set! twice #t))))))
        loc))

    (define (delete-to-zero forwards)
      (let ((loc (cursor))
    	(zero-loc (find-zero forwards)))
        (if (not (= loc zero-loc))
    	(if forwards
    	    (delete-samples (cursor) (- zero-loc loc))
    	    (begin
    	      (delete-samples zero-loc (- loc zero-loc))
    	      (set! (cursor) zero-loc))))))

    (define (go-to-zero forwards)
      (set! (cursor) (find-zero forwards)))

    (bind-key (char->integer #\k) 4 (lambda (arg)
    				  (do ((i 0 (1+ i)))
    				      ((= i (abs arg)))
    				    (delete-to-zero (> arg 0)))))
    (bind-key (char->integer #\n) 4 (lambda (arg)
    				  (do ((i 0 (1+ i)))
    				      ((= i (abs arg)))
    				    (go-to-zero (> arg 0)))))
    (bind-key (char->integer #\p) 4 (lambda (arg)
    				  (do ((i 0 (1+ i)))
    				      ((= i (abs arg)))
    				    (go-to-zero (< arg 0)))))

| Most of the predefined key definitions are given in Keyboard Commands.
`clear-audio-inputs ()`
| in Linux/OSS, try to reduce soundcard background racket.
`close-sound-file (fd bytes)`
| close file updating header to report bytes bytes of data.
`c-g? ()`
| check for C-g to interrupt on-going computation (and let other UI events through).
`c-g! ()`
| simulate typing C-g (intended for use with bind-key to remap C-g).
`defvar (var val)`
| same as `(define var val)` except that the envelope editor keeps track of var thereafter and treats lists as envelopes. (defvar is a macro). I'm using defvar here rather than some more perspicuous name like def-envelope so that Snd and CLM can share envelope files.
`equalize-panes (snd)`
| equalize Snd panes as in View menu Equalize Panes option. If the snd argument is given, only that sound's panes are affected.

    (add-hook! after-open-hook
      (lambda (n) (if (> (channels n) 3) (equalize-panes))))

`exit (exit-value)`
| exit Snd.
`graph->ps (file)`
| create Postscript description of current display file defaults to eps-file. [cannot-print].
`in (ms thunk)`

ms milliseconds from now, evaluate thunk, a function of no arguments.

    (in 5000 (lambda () (snd-print "boo!")))

    (define (at hour minute func)
      (let* ((cur-time (localtime (current-time)))
    	 (cur-minute (vector-ref cur-time 1))
    	 (cur-hour (vector-ref cur-time 2))
    	 (now (+ (* cur-hour 60) cur-minute))
    	 (then (+ (* hour 60) minute)))
        (in (* 1000 60 (- then now)) func)))

    (at 15 11 (lambda () (snd-print "it's 3:11 pm!")))

`key (key state snd chn)`
| execute the keyboard command key with modifier keys state. shift: 1, control: 4, meta: 8
`key-binding (key state extended)`
| the (user-defined, not built-in) procedure currently bound to key+state+extended.
`memo-sound`

When a sound file is opened, Snd looks for a file with the same name but with an appended ".scm" extension. If such a file is found, it is loaded automatically. The variable memo-sound is set to the newly opened sound's index. This supports the "snd-memo" feature in CLM, but can be used independently of CLM to store marks, selections, or whatever that you want associated with a particular sound. Confusingly enough, this is a Scheme variable, unlike all the others -- that is, you refer to it directly, not as a procedure call.

`open-sound-file (name chans srate comment)`
| Open (create) a sound file name (defaults to "test.snd" or "test.wav"). It is assumed that the data will be floats in the native format (written by the caller interleaving channels), and that the file will be closed by close-sound-file. One simple way to write the data is to call vct->sound-file. open-sound-file opens a file external to Snd, where open-sound loads a file into Snd for editing.
`preload-directory (dir)`
| preload sound files from directory dir (see -p).
`preload-file (file)`
| preload file (see View menu's View Files option).
`reset-listener-cursor`
| reset listener cursor to the default pointer shape (needed by snd-debug and similar procedures to cancel the wait cursor).
`save-envelopes (filename)`
| save envelope editor contents in filename [cannot-save].
`save-listener (filename)`
| save listener contents in filename [cannot-save].
`save-macros ()`
| save keyboard macros in Snd's init file (~/.snd). [cannot-save].
`save-options (filename)`
| save options in filename [cannot-save].
`save-state (filename)`
| save current state of Snd in filename [cannot-save].
`script-arg ()`
| current startup argument number (normally 1). See Snd as a script engine and snd-test.scm for examples.
`script-args ()`
| startup arguments as a list of strings. See Snd as a script engine and snd-test.scm for examples.
`snd-apropos (name)`

return possible completions of name (a string or a symbol).

    :(snd-apropos "mouse-enter")
    (guile): mouse-enter-graph-hook
    (guile): mouse-enter-label-hook
    (guile): mouse-enter-listener-hook

`snd-error (str)`
| report error message str, return str.
`snd-help (obj)`
| returns (as a string) the help text associated with obj, for example: `(snd-help vct-ref)`. snd-help this returns the help text associated with obj. To go to the HTML documentation for a given object, load index.scm and use the html function. To get a more global help function, `(use-modules (ice-9 session))`. This loads Guile's help (and apropos) support which uses 'regexps' and so forth.
`snd-print (str)`
| display str in lisp listener, return str. (This is intended as a debugging aid -- there's still nothing like a lowly print statement).
`snd-remember-paths`
| A Scheme variable (not a function); if #t (default is #f), Snd adds code to the Guile %load-hook that makes sure the current file's path is included in %load-path when load or load-from-file is called. This makes it possible to use load-from-path inside a scheme file when you don't know in advance where that file will reside at load time.
`snd-spectrum (data window length linear)`
| return spectrum (vct) of data (also a vct) using fft-window win. length of data (and fft) is length. `(let ((spectr (snd-spectrum data rectangular-window (transform-size)))) ...)` If linear is #f (default is #t), the spectrum is returned as dB values.
`snd-tempnam ()`
| New temp file name using Snd's temp-dir.
`snd-version ()`
| Snd version (a string).
`snd-warning (str)`
| report warning message str, return str.
`sound-files-in-directory (dir)`
| return a list of sound file names. A file is considered a sound file if it is non-empty and its extension is on the sound file extension list (see add-sound-file-extension). The directory name defaults to the current directory. This is useful for "batch" processing of sounds. The following, for example, prints the names of all the stereo AIFC files it finds:

    (for-each
      (lambda (filename)
        (if (and (= (mus-sound-header-type filename) mus-aifc)
                 (= (mus-sound-chans filename) 2))
            (snd-print (format #f "~%~A" filename))))
      (sound-files-in-directory))

| See also map-sound-files in snd4.scm.
`unbind-key (key state extended)`
| cause key with modifiers state to be a no-op.
`widget-position (widget)`
| return a list giving the widget x and y coordinates.
`widget-size (widget)`
| return a list giving the widget width and height.

      change-property (known-atom property value)
      listener-selection ()
      colormap-ref (map position)
      snd-gcs
      snd-pixel

change-property changes an X-window's property; there's an example in examp.scm. listener-selection returns the currently selected text in the listener, or #f if there isn't any. The following posts help related to the selection if "h" is typed in the graph:

      (bind-key (char->integer #\h) 0
        (lambda ()
          (let ((subject (listener-selection)))
    	(if subject
                (help-dialog subject (snd-help subject))))))

colormap-ref returns the rgb values of the colormap map at position pos, suitable for use with make-color. snd-gcs returns a list of Snd's graphics contexts (intended for use with the xm module in snd-motif.scm). snd-pixel returns the pixel associated with a give color such as basic-color. This is handy in snd-motif.scm where many of the procedures want an X-style Pixel value.

related documentation snd.html grfsnd.html clm.html sndlib.html sndscm.html index.html