| this file: | grfsnd.html: | |
|---|---|---|
|
Snd is a highly customizable, extensible program. I've tried to bring out to the extension language nearly every portion of Snd, both the signal-processing functions and much of the user interface. You can, for example, add your own menu choices, editing operations, or graphing alternatives. Nearly everything in Snd can be set in an initialization file, loaded at any time from a text file of program code, or specified in a saved state file. It can also be set via inter-process communication or from stdin from any other program (CLM and Emacs in particular), embedded in a keyboard macro, or typed in the listener.
The syntax used throughout this documentation is Scheme (a form of lisp) as implemented by s7. You can also use Ruby or Forth, but need to make various minor changes. I'm slowly adding parallel Forth and Ruby examples.
The easiest way to get acquainted with this aspect of Snd is to open the listener (via the View:Open listener menu option), and type experiments in its window. Its prompt is ">". So, say we've opened the listener.
| Scheme | Ruby | Forth |
|---|---|---|
> (+ 1 2) 3 | >1+2 3 | >1 2 + 3 |
> (open-sound "oboe.snd") #<sound 0> |
>open_sound("oboe.snd")
0
| >"oboe.snd" open-sound 0 |
> (auto-resize) #t | >auto_resize true | >auto-resize #t |
> (set! (auto-resize) #f) #f | >set_auto_resize false false | >#f set-auto-resize #f |
> (set! (x-bounds) '(0.0 1.0)) (0.0 1.0) | >set_x_bounds([0.0, 1.0]) 0.01.0 | >'( 0.0 1.0 ) set-x-bounds '( 0.0 1.0 ) |
> (load "bird.scm") make-birds | >load "bird.rb" true | >"bird.fs" file-eval 0 |
> (map-channel (lambda (y) (* y 2))) 0 | >map_channel(lambda do |y| y * 2 end) -0.0015869140625 |
>lambda: <{ y }> y 2.0 f* ; map-channel
-0.00158691
|
> (define (plus a b) (+ a b)) plus | >def plus(a, b) a+b end |
>: plus ( a b -- sum ) { a b } a b + ;
nil
|
> (set! (basic-color) (make-color 1 0 0)) (Pixel 16711680) | >set_basic_color make_color(1, 0, 0) [:Pixel, 16711680] | >1 0 0 make-color set-basic-color #<XmRaw: Pixel 0x9d3b430> |
Another quick way to check out the extension language is to go to the Preferences dialog (in the Options menu), choose some items, then save them. The saved file (~/.snd_prefs_s7 for example) is a text file, a program in the current extension language, that initializes Snd to use whatever items you chose.
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, and regions; the currently active region is called the selection. I originally presented all the functions and variables in an enormous alphabetical list, but that finally became unmanageable. In the following sections, each of the basic entities is treated in a separate section with cross-references where needed. The index provides alphabetical entry.
There are many examples in examp.scm, examp.rb, examp.fs, and snd-test.scm. Extensions to Snd can be found in:
|
Most of Snd's behavior can be customized. For example, when a sound is saved, some people want to be warned if a pre-existing sound is about to be destroyed; others (Snd's author included) grumble "just do it". There are two ways this kind of situation is handled in Snd; through global variables and hooks. A hook is a list of callbacks invoked whenever its associated event happens. When Snd exits, for example, any functions found on the before-exit-hook list are evaluated; if any of them returns #t, Snd does not exit.
(define (unsaved-edits? lst)
(and (pair? lst)
(if (> (car (edits (car lst))) 0)
(begin
(status-report "there are unsaved edits")
#t)
(unsaved-edits? (cdr lst)))))
(hook-push before-exit-hook
(lambda (hook)
(set! (hook 'result) (unsaved-edits? (sounds)))))
Now when Snd is told to exit, it checks before-exit-hook, runs unsaved-edits?, and if the latter returns #t, if prints a worried message in the status area, 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.
The global variables handle various customizations that aren't callback-oriented. For example, as sounds come and go, Snd's overall size may change (this is partly determined by the window manager, but is also up to Snd). Many people find this distracting — they would rather that the overall window stick to one size. The Snd variable associated with this is "auto-resize"; it can be accessed as:
Scheme: *auto-resize* or (auto-resize) Ruby: auto_resize() Forth: auto-resize
and set via:
Scheme: (set! *auto-resize* #f) or (set! (auto-resize) #f) Ruby: set_auto_resize(false) Forth: #f set-auto-resize
I originally wanted a variable like auto-resize to look like a variable in source files, but
in those bygone days (1996), that was not possible: I had to use a dumb "procedure-with-setter" instead.
So you'd sigh and access auto-resize by calling it as a function: (set! (auto-resize) #t).
Now in s7, the same thing is also named *auto-resize*, but it's finally a variable,
so you can use (set! *auto-resize* #t). The documentation of course
is out of date, and mostly uses the old form. In any case,
the statement (set! *auto-resize* #f) can be placed in your ~/.snd initialization file
to make it the default setting for your version of Snd, or placed
in a separate file of Scheme code and loaded at any time via the load
function.
The functions affecting Snd's overall behavior are given below; in s7 there is also the variable of the same name, but with "*" around it: *auto-resize*.
ask-about-unsaved-edits (default: #f)
If ask-about-unsaved-edits is #t, Snd worries about unsaved edits when a sound is about to be closed.
Otherwise Snd just closes the sound, flushing any unsaved edits.
(set! (ask-about-unsaved-edits) #t)
ask-before-overwrite (default: #f)
ask-before-overwrite determines whether Snd asks before overwriting an existing file:
(set! (ask-before-overwrite) #t)
auto-resize (default: #t in Motif
auto-resize determines whether the Snd window should be resized when a sound is opened or closed.
auto-update (default: #f)
auto-update determines whether Snd should update a file automatically if it (the file) changes on disk due to some other process. If Snd's view of a sound doesn't match the on-disk version of the sound, a warning is posted that there are two conflicting versions of the sound.
auto-update-interval (default: 60)
This is the time (in seconds) between background checks for a changed file on disk (see auto-update). If auto-update-interval is 0.0, the auto-update background process is turned off.
clipping (default: #f)
If clipping is #t, output values are clipped to fit the current sndlib sample representation's notion of -1.0 to just less than 1.0. The default (#f) can cause wrap-around (if writing integer sound data) which makes the out-of-range values very obvious. To control this action more closely, use clip-hook. To get completely confused, see mus-clipping and mus-file-clipping: this has become as messed up as the sampling rate settings!
cursor-location-offset (default: 0.05)
cursor-location-offset is the offset in samples between Snd's notion of the location of the tracking cursor (with-tracking-cursor in Snd jargon) and the actual (DAC-relative) location. Since, in general, Snd can't tell how many samples of buffering there are between itself and the speakers (audio cards have varying amounts), its notion of where to place the tracking cursor can be wrong by an almost arbitrary amount. If you have some idea of the buffering amount, you can correct this error via cursor-location-offset.
cursor-update-interval (default: 0.05)
This is the time in seconds between cursor redisplays if playing a sound with with-tracking-cursor #t. If this number is too small, you may clicks during playback.
dac-combines-channels (default: #t)
If dac-combines-channels is #t, and the current sound has more channels than are supported by the available audio hardware, Snd mixes the extra channels into the available channels during audio output. This provides a way to hear 4-channel sounds when you only have a stereo audio card. If dac-combines-channels is #f, extra channels are not played.
dac-size (default: 256)
dac-size is the audio output buffer size; it is not always meaningful. See play-with-envs in enved.scm. When you change the control panel settings during playback, the snappiness of the response is set, to some extent, by the dac-size. The default of 256 gives a stair-case effect in many cases, whereas 2048 is smoother. This also affects the resampling smoothness of playback while dragging the mark play triangle. Some audio choices, ALSA in particular, may ignore dac-size.
default-output-chans (default: 1)
default-output-chans is the default number of channels when a new or temporary file is created, or a save dialog is opened.
default-output-header-type (default: mus-next)
This is the default header type when a new file is created, or a save dialog is opened. (The default, mus-next, stands for the NeXT/Sun sound file header). The available output header-types are:
mus-next mus-aifc mus-riff mus-rf64 mus-nist mus-raw mus-ircam mus-aiff
mus-soundfont mus-bicsf mus-voc mus-svx mus-caff
default-output-sample-type (default: mus-bfloat)
default-output-sample-type is the default sample type when a new or temporary file is created, or a save dialog is opened. (The default, mus-bfloat, is from sndlib, standing for 32-bit big-endian floating point data). Use mus-out-format for fastest IO. The available output sample types are (b=big-endian, l=little, u=unsigned, short=16 bits, byte=8 bits, int = 32 bits):
mus-bshort mus-lshort mus-mulaw mus-alaw mus-byte mus-ubyte mus-bfloat
mus-lfloat mus-bint mus-lint mus-b24int mus-l24int mus-bdouble mus-ldouble
mus-ubshort mus-ulshort
There are also "unscaled" versions of the floating point types, and "normalized" versions of the integers.
default-output-srate (default: 44100)
This is the default sampling rate when a new or temporary file is created, or a save dialog is opened. It also sets the default CLM srate (*clm-srate* in ws.scm), so all CLM generators use it outside with-sound.
eps-bottom-margin (default: 0.0)
eps-bottom-margin is the bottom margin used in snd.eps, created by the File:Print dialog, or the graph->ps function. PostScript units are 1/72 of an inch (a "point" in printer jargon); an inch is 2.54 cm:
Scheme: (define (inches-to-ps inches) (* inches 72)) (define (cm-to-ps cm) (* cm (/ 72.0 2.54))) |
Ruby: def inches_to_ps(inches) inches * 72 end def cm_to_ps(cm) cm * 72.0 / 2.54 end |
Forth:
: inches-to-ps { inches }
inches 72 f*
;
: cm-to-ps { cm }
cm 2.54 f/ 72 f*
;
|
In the resulting .eps file, you'll find a concat statement near the top of the file; the first and fourth numbers are scale factors on the entire graph, the fifth is the left margin, and the sixth is the bottom margin.
eps-file (default: "snd.eps")
This is the default name of the Postscript file produced by the File:Print dialog, or the graph->ps function.
eps-left-margin (default: 0.0)
eps-left-margin is the left margin used in snd.eps, created by the File:Print dialog, or the graph->ps function.
eps-size (default: 1.0)
eps-size is the scaler used to set the overall picture size in snd.eps, created by the File:Print dialog, or the graph->ps function,
graph-cursor (default: XC_crosshair (34))
graph-cursor is the kind of cursor displayed following the mouse in the data graph.
It can be any of the cursors provided by X: (set! (graph-cursor) 22).
The X/Motif cursors are declared in /usr/include/X11/cursorfont.h or some such file.
Some useful choices are:
Motif value
XC_center_ptr 22
XC_cross 30
XC_crosshair 34
XC_left_ptr 68
XC_plus 90
XC_right_ptr 94
XC_tcross 130
XC_xterm 152
html-dir (default: ".")
html-dir is the directory to search for documentation if an HTML reader is in use. See the function html in index.scm.
html-program (default: "firefox")
This is the program to use to read HTML files. On the Mac, you need to give the full path to the executable image: "/Applications/Safari.app/Contents/MacOS/Safari". See the function html in index.scm.
initial-beg (default: 0.0)
initial-beg is the start point (in seconds) of the initial graph of a sound.
initial-dur (default: 0.1)
initial-dur is the duration (in seconds) of the initial graph of a sound.
just-sounds (default: #t)
If just-sounds is #t, the file lists displayed by the file selection dialogs are filtered to show just sound files (see add-sound-file-extension).
ladspa-dir (default: #f)
LADSPA is a way of managing plug-ins in Linux. I consider it very old-fashioned, but there are a bunch of ladspa libraries, and they're easy to load. so... ladspa-dir is the name of the directory to search for LADSPA plugin libraries (it can override or replace LADSPA_PATH). See Snd and LADSPA.
log-freq-start (default: 32.0)
log-freq-start is the start (lowest) frequency used in the log freq display (ffts). Since the log display emphasizes the lower frequencies, but the lowest are all inaudible, it seemed more informative to squash the lowest 30Hz or so into a single point (0 Hz) on the log freq graphs; otherwise the audible data starts about 1/4 of the way down the x axis, wasting valuable screen space! But it also seemed a bother to have to set/reset the spectrum-start variable every time you wanted to flip between log and linear displays. log-freq-start to the rescue? For other ideas along these lines, see display-bark-fft.
max-regions (default: 16)
This sets the maximum size of the region list, the number of regions that are accessible.
mus-max-malloc (default: 67108864)
This sets the maximum memory allocation (in bytes). In s7, you can use the variable *mus-max-malloc* instead.
mus-max-table-size (default: 20971520)
This sets the maximum table size (delay line length in samples, etc). In s7, you can use the variable *mus-max-table-size* instead.
mus-sound-path (default: ())
This is the list of directories searched for an incompletely specified sound file. The current directory is always searched first.
open-file-dialog-directory (default: ".")
open-file-dialog-directory is the name of the initial open file dialog directory (normally ".").
peak-env-dir (default: #f)
peak-env-dir is the directory to use for peak env files; if it is #f (the default), the peak-env machinery is turned off. If the peak-env-dir is writable, Snd saves an overview of the sound data in that directory to speed up the initial graph display the next time you open that sound.
play-arrow-size (default: 10)
This is the size of the triangular arrow that handles click-to-play duties for the cursor, marks, mixes, and the selection.
print-length (default: 12)
For objects such as float-vectors, print-length sets the number of elements printed. In s7, this also sets (*s7* 'print-length).
> (set! (print-length) 3) 3 > (make-float-vector 10 .1) #(0.1 0.1 0.1 ...)
remember-sound-state (default: #f)
If remember-sound-state is #t, Snd saves most of a sound's display state when it is closed, and if that same sound is later re-opened, restores the previous state.
save-dir (default: #f)
save-dir is the name of the directory for saved-state files. These files are written when you call save-state or choose the Options:Save session menu item. If any of the current sounds has an edit that requires saved data, it is written as a separate sound file, and that file is reloaded automatically when you restart the saved session. To keep such files safe, or at least separate from others, you can set up separate directory for them.
save-state-file (default: "saved-snd.scm")
This is the saved state file name.
search-procedure
This is the search procedure used by the find dialog or C-s if none is otherwise specified.
(set! (search-procedure) (lambda (y) (> y .1)))
selection-creates-region (default: #t)
If selection-creates-region is #t, a region is created whenever a selection is made. If you're editing very large sounds and using selections, the region temp files can use up a lot of disk space (and the time to write them); if you're not using regions anyway, this switch can turn them off.
show-full-duration (default: #f)
If show-full-duration is #t, Snd displays the entire sound when it is first opened.
show-full-range (default: #f)
If show-full-range is #t, Snd makes sure the sound's graph y bounds can accommodate the entire range of sample values. This is especially useful when you're working with sounds that go beyond the normal -1.0 to 1.0 range.
show-indices (default: #f)
If show-indices is #t, each sound's name is preceded by its index in the sound pane.
show-selection-transform (default: #f)
If show-selection-transform is #t, Snd displays the transform of the current active selection, if any. The sonogram and spectrogram displays ignore this flag because they assume their time axis matches that of the time domain graph.
sinc-width (default: 10)
sinc-width is the width in samples of the sampling rate conversion sinc interpolation.
The higher this number, the better the src low-pass filter, but the slower
src runs. If you use too low a setting, you can sometimes hear high
frequency whistles leaking through. To hear these on purpose, make
a sine wave at (say) 55 Hz, then (src-sound '(0 3 1 1))
with sinc-width at 4.
sync-style (default: sync-by-sound)
sync-style determines how sounds and their channels are sync'd together when they are opened. sync-none means that no sounds, and no channels are tied together; sync-all causes everything to be tied together; sync-by-sound, the default, causes individual sounds to be separate, but if they have more than one channel, all the channels are tied together.
temp-dir (default: #f)
temp-dir is the directory to use for temporary files; if it is #f, Snd uses whatever the system default is, usually "/tmp" or "/var/tmp". See also snd-tempnam.
window-height (default: 0)
window-height is the current Snd window height in pixels. This is the same as
Scheme: (cadr (widget-size (cadr (main-widgets)))) Ruby: widget_size(main_widgets.cadr).cadr Forth: main-widgets cadr widget-size cadr
except at startup when the window-height function and friends defer the assignment until after the main widgets
have been created. If Snd becomes confused about screen size, it can make its main window so large that
you can't get at any of the decorations for resizing the window; in this emergency you can
(set! (window-height) 300) or some such number.
window-width (default: 0)
This is the current Snd window width in pixels.
window-x (default: -1)
This is the current Snd window left side position in pixels (-1 means unset).
This is (usually) the same as
(car (widget-position (cadr (main-widgets)))).
window-y (default: -1)
This is the current Snd window upper side position in pixels (X numbering starts at 0 at the top, -1 means unset).
with-background-processes (default: #t)
with-background-processes determines whether Snd should use background (idle time) processes for ffts and so forth. It is intended primarily for auto-testing.
with-file-monitor (default: #t)
If with-file-monitor is #t, the file alteration monitor is active. There are still bugs in this library that can cause Snd to hang — I haven't tracked down what the problem is yet; in the meantime, set this switch to #f to disable the monitor.
with-inset-graph (default: #f)
If with-inset-graph is #t, a small graph is added to the upper right corner showing the overall current sound and where the current window fits in it. This information is implicit in the x axis zoom and position sliders, but a redundant graph doesn't hurt. If you click in that graph, the cursor is moved to the clicked point.
with-interrupts (default: #t)
If with-interrupts is true, the Snd listener (in Motif) adds a check for GUI activity to each computation called from the listener. This makes it possible to stop an infinite loop, or use the user interface while some long computation is running, but it also slows down that computation. To get the maximum performance, set this flag to false (#f).
with-menu-icons (default: #t)
with-menu-icons determines whether some menus display icons beside the item labels.
with-pointer-focus (default: #f)
If with-pointer-focus is #t, whatever text or graph widget is underneath the mouse cursor is activated (this is sometimes known as "point-to-focus" mode).
with-relative-panes (default: #t)
If with-relative-panes is #t in the Motif version of Snd, a multichannel sound tries to retain the relative channel graph sizes when the outer sash (the overall sound size sash) changes. Mono sounds and the listener are not affected (perhaps they should be?).
with-smpte-label (default: #f)
with-smpte-label shows the current SMPTE frame number in a box in the upper left corner of the graph (see the picture above under add-mark-pane).
with-toolbar (default: #f (motif))
with-toolbar places a toolbar at the top of the Snd window, just under the main menu.
with-tooltips (default: #t)
Set with-tooltips to #f to turn off tooltips.
with-tracking-cursor (default: #f)
This is #t if the cursor always follows along in the sound during playback. If it is #f, you get the tracking cursor displayed only when you ask for it (via control-click of the play button, for example). At the end of the play, the default is to return to the original (starting) cursor position. If you want the cursor to stay where it is, set with-tracking-cursor to :track-and-stay (#t = :track-and-return).
The interval (in seconds) between cursor updates is set by cursor-update-interval which defaults to 0.05. The accuracy of the cursor in reflecting the sound coming out the speakers depends on the amount of buffering in your audio system. If Snd's displayed location is off, set cursor-location-offset to reflect the number of samples of buffering you think you probably have. A positive cursor-location-offset delays the cursor's apparent progress (if playing forwards).
| tracking cursor |
|---|
play from the current cursor position with a tracking cursor: pfc |
zoom-focus-style (default: zoom-focus-active)
This determines what a zoom action focuses (centers) on. The choices are zoom-focus-left, zoom-focus-right, zoom-focus-active, zoom-focus-middle, or a function of 6 arguments. The function should return the new window left edge as a float. Its arguments are the current sound, channel number, zoom slider value (0.0 to 1.0), time domain window left and right edges in seconds, and the current total x axis size (seconds) corresponding to a slider value of 1.0.
(set! (zoom-focus-style) (lambda (snd chn zx x0 x1 range) (- x1 (* zx range))))
mimics zoom-focus-right. zoom-focus-active tries to focus on some object in the view: the cursor, a mix or mark, etc. See also Zoom options.
Several functions in Snd are "generic" in the sense that they can handle a wide variety of objects. The length function, for example, applies to strings and vectors, as well as lists. Objects specific to Snd include sounds, the selection, mixes, marks, samplers, regions, and players, all of which should be compared with equal?, not eq?.
channels obj
channels handles strings (mus-sound-chans), region-chans, the current selection (selection-chans), mus-channels, mixes, float-vectors, and vectors (always 1 channel), and sounds (as objects or as integers).
copy obj
copy returns a copy of its argument. It works with strings, lists, vectors, hash tables, float-vectors, sounds, the current selection, mixes, marks, bignums, and the non-gmp random state objects.
file-name obj
filename can replace mus-expand-filename, mus-file-name, and (s7 scheme's) port-filename, as well as handling mixes, regions, samplers, and the regular sound-oriented file-name.
fill! obj val
fill! fills obj with val (s7 only). fill! works with strings, vectors, hash-tables, float-vectors, lists, sounds, and the selection.
framples obj chn edpos
framples returns the number of "framples" in an object, that is, the number of samples per channel (a frample is a set of samples, representing each channel's value at a given sampling instant, a "sample frame" or a "frame of samples" in old time terminology).
The framples function overlaps the length function, but length of a string is string-length, whereas framples of a string treats the string as a sound file name and returns mus-sound-framples. framples can replace mus-sound-framples, mus-length, mix-length, region-framples, selection-framples, and the regular framples function that handles sound objects and integers as sound indices.
length obj
length handles list length, string-length, vector-length, framples (sound length), colormap-size, mus-length (generators), mix-length, transform-size, selection-framples, and region-framples.
maxamp obj chn
maxamp can handle a sound (via the regular maxamp function), string (treated as a sound file name, mus-sound-maxamp), generator (maxamp of the mus-data float-vector, if any), float-vector, region (region-maxamp), the current selection (selection-maxamp), vector, list, or mix object.
play object :start :end :channel :edit-position :out-channel :with-sync :wait :stop :srate :channels
play plays an object. The object can be a string (sound filename), a sound object or index, a mix, a region, the selection object, #f, a procedure, or a player. Not all the keyword arguments apply in all cases, though I hope to fill in the table of possibilities eventually. The full documentation is currently under play.
srate obj
srate handles strings (treated as file names: mus-sound-srate), regions (region-srate), the selection (selection-srate), and sounds (as objects or as integers).
sync obj
sync accesses the 'sync' field of a sound, mark, or mix.
When some user-interface action takes place, code is called that responds to that action; these functions are sometimes called callbacks; the variable that holds a list of such callbacks is known as a hook. For example, the hook that is checked when you click the sound's name in the status area is name-click-hook. We can cause that action to print "hi":
Scheme: (hook-push name-click-hook (lambda (hook) (snd-print "hi") (set! (hook 'result) #t)))
Ruby: $name_click_hook.add_hook!("print") do |snd| snd_print("hi"); true end
Forth: name-click-hook lambda: <{ snd }> "hi" snd-print drop #t ; add-hook!
The Scheme hook function is slightly different from the Forth and Ruby cases. For about 15 years, Snd used Guile-style
hooks which are essentially a list of functions, each called with the hook arguments ('snd' above). But now Scheme hooks
are functions that have three internal lists of functions. The internal functions (the things we add to hook-functions, for example)
take the hook's internal environment as their only argument ('hook' above), and then access the actual hook arguments
via (hook name). So, the examples are confusing because the situation is confused! To move between the
versions, match the Forth/Ruby argument name to the Scheme hook variable, so the function argument 'snd in Forth/Ruby
is (hook 'snd) in Scheme.
In Ruby and Forth, but not in Scheme, if there is more than one function attached to a hook, some of the hooks "or" the functions together; that is they run through the list of functions, and if any function returns something other than #f, the hook invocation eventually returns the last such non-#f value. A few hooks are "cascade" hooks; that is, each function gets the result of the previous function, and the final function's value is returned. In other cases the result returned by the hook is the result of the last function in the list. Whatever the hook combination choice, all the functions on the hook list are run on each invocation.
In Scheme, all functions are run, each takes one argument, the hook environment, and any return values are ignored. It is
up to the individual functions to track (hook 'result) if intermediate results matter.
There are several basic actions that involve a bunch of hooks. Here is a schematic view of some of these sequences.
Open filename
bad header?: bad-header-hook — can cancel request
no header?: open-raw-sound-hook — can cancel request
file ok:
open-hook — can change filename
file opened (no data read yet)
during-open-hook (can set prescaling etc)
data read, no graphics yet
after-open-hook
initial-graph-hook
Save current sound
before-save-as-hook — can cancel the request or set its output parameters
save-hook
sound saved
if any sample is clipped during save, clip-hook
after-save-as-hook
Play sound
when a play request occurs: start-playing-hook — can cancel the request, also start-playing-selection-hook
(any number of sounds can be playing at once)
as each sound ends: stop-playing-hook, stop-playing-selection-hook
Close sound
before-close-hook — can cancel close
close-hook (sound is still open)
sound closed
Save current Snd ("session") state
save-state-hook — can change output filename (crummy name is an historical artifact)
output save-state file opened
before-save-state-hook
Snd saves its state
after-save-state-hook
output closed
Exit Snd
before-exit-hook — can cancel exit request
exit-hook
Snd cleans up and exits
Here's the Ruby version of some of the hook-related functions:
$var_hook.remove_hook!("proc_name")
$var_hook.reset_hook!
$var_hook.run_hook do |prc| prc.call(1, 2, 3) end
$var_hook.call(1, 2, 3) # calls all procedures
require 'hooks'
$var_hook.show # prints the code of the procedure(s)
$va_hook.to_a
And some Forth examples, taken from Mike Scholz's documentation:
open-hook ' open-buffer 1 make-proc add-hook!
open-hook "open-buffer" remove-hook!
open-hook reset-hook!
open-hook hook->list
2 "A simple hook." create-hook my-new-hook
my-new-hook ' + 2 make-proc add-hook!
my-new-hook '( 2 3 ) run-hook
help my-new-hook
These hooks are extremely easy to add; if there's some user-interface action you'd like to specialize in some way, send me a note. hooks.scm has snd-hooks and reset-all-hooks, as well as other useful hook-related functions.
In the following list of hooks, the arguments after the hook name refer to the arguments to the functions invoked by
the hook (in Ruby and Forth). That is, after-apply-controls-hook (snd) means that the functions on the
after-apply-controls-hook list each take one argument, a sound. In Scheme, they refer to the hook variables accessible in the hook environment via (hook 'snd), for example.
after-apply-controls-hook (snd)
This hook is called when apply-controls finishes. add-amp-controls in snd-motif.scm uses this hook to reset any added amplitude sliders to 1.0.
after-graph-hook (snd chn)
This hook is called after a graph is updated or redisplayed. Use it to add your own finishing touches to the display; if added earlier they risk being erased by Snd as it redraws graphs.
after-lisp-graph-hook (snd chn)
This hook is called after a "lisp" graph is updated or redisplayed. The lisp-graph-hook functions are called before the actual graph is displayed, so if you want to add to a graph in some way, you need to use after-lisp-graph-hook. display-bark-fft in dsp.scm uses it to draw the x axis labels and ticks for various frequency scales.
after-open-hook (snd)
This hook is called just before a newly opened sound's window is displayed. It provides a way to set various sound-specific defaults. For example, the following causes Snd to default to locally sync'd channels (that is, each sound's channels are sync'd together but are independent of any other sound), united channels (all chans in one graph), and filled graphs (not line segments or dots, etc):
(hook-push after-open-hook
(lambda (hook)
(let ((snd (hook 'snd)))
(if (> (channels snd) 1)
(begin
(set! (sync snd) (+ 1 (sync-max)))
(set! (channel-style snd) channels-combined)
(set! (graph-style snd) graph-filled))))))
See also enved.scm, and various examples in snd-motif.scm.
after-save-as-hook (snd name dialog)
This hook is called after File:Save as. ('snd = sound index, 'name = full filename, 'dialog = #t if called from a dialog).
after-save-state-hook (name)
This hook is called after Snd has saved its state (save-state). 'name is the (otherwise complete) saved state program (a filename). See ws-save-state in ws.scm. It uses this sequence:
(let ((fd (open-output-file filename "a"))) ; "a" = append (format fd "~%~%;;; from ws.scm~%") ... (close-output-port fd))
after-transform-hook (snd chn scaler)
This hook is called just after an FFT (or spectrum) is calculated.
(define (report-fft-peak snd chn)
(if (and (transform-graph?)
(= (transform-graph-type) graph-once))
(status-report
(number->string (/ (* 2 (maxamp (transform->float-vector snd chn)))
(transform-size snd chn))))))
(hook-push after-transform-hook
(lambda (hook)
(report-fft-peak (hook 'snd) (hook 'chn))))
bad-header-hook (name)
This hook is called if a file has a bogus-looking header (that is, a header with what appear to be bad values such as a negative number of channels). If the hook returns #t, Snd does not try to open the file.
(hook-push bad-header-hook
(lambda (hook)
(set! (hook 'result) #t))) ; don't open bogus-looking files
If no header is found, open-raw-sound-hook is invoked instead ("raw" = "headerless").
before-close-hook (snd)
This hook is called when a file is about to be closed. If the hook returns #t, the file is not closed.
before-exit-hook ()
This hook is called upon a request to exit Snd. If the hook returns #t, Snd does not exit.
before-save-as-hook (snd name selection sampling-rate sample-type header-type comment)
This hook is called before save-sound-as or File:Save as. If the hook returns #t, the save is not performed. This hook provides a way to do last minute fixups (srate conversion for example) just before a sound is saved. The arguments to the hook function describe the requested attributes of the saved sound; 'snd' is the to-be-saved sound's index; 'name' is the output file's name; 'selection' is #t if we're saving the selection.
(hook-push before-save-as-hook
(lambda (hook)
(let ((index (hook 'snd))
(filename (hook 'name))
(sr (hook 'sampling-rate))
(dformat (hook 'sample-type))
(htype (hook 'header-type))
(comment (hook 'comment)))
(if (not (= sr (srate index)))
(let ((chns (channels index)))
(do ((i 0 (+ i 1)))
((= i chns))
(src-channel (* 1.0 (/ (srate index) sr)) 0 #f index i))
(save-sound-as filename index :header-type htype :sample-type dformat :srate sr :comment comment)
(do ((i 0 (+ i 1)))
((= i chns))
(undo 1 index i))
(set! (hook 'result) #t)))))) ; tell Snd that the sound is already saved
before-save-state-hook (name)
This hook is called before Snd saves its state (save-state). 'name' is the saved state file. If the hook returns #t, the save state file is opened in append mode (rather than create/truncate), so you can write preliminary stuff via this hook, then instruct Snd not to clobber it during the save process.
(hook-push before-save-state-hook
(lambda (hook)
(call-with-output-file (hook 'name)
(lambda (p)
(format p ";this comment will be at the top of the saved state file.~%~%")))
(set! (hook 'result) #t)))
before-transform-hook (snd chn)
This hook is called just before an FFT (or spectrum) is calculated. If the hook returns an integer, that value is used as the starting point (sample number) of the fft. Normally, the fft starts from the left window edge. To have it start at mid-window:
(hook-push before-transform-hook
(lambda (hook) ; 0.5 * (left + right) = midpoint
(set! (hook 'result)
(round (* 0.5 (+ (right-sample (hook 'snd) (hook 'chn))
(left-sample (hook 'snd) (hook 'chn))))))))
The following somewhat brute-force code shows a way to have the fft reflect the position of a moving mark:
(let ((fft-position #f))
(hook-push before-transform-hook
(lambda (hook)
(set! (hook 'result) fft-position)))
(hook-push mark-drag-hook
(lambda (hook)
(set! fft-position (mark-sample (hook 'id)))
(update-transform-graph))))
clip-hook (val)
This hook is called whenever a sample is about to be clipped while writing out a sound file. The hook can return the new value.
close-hook (snd)
This hook is called when a file is closed (before the actual close, so the index 'snd' is still valid).
(hook-push close-hook
(lambda (hook)
(play "wood16.wav")))
color-hook ()
This hook is called whenever one of the variables associated with the color dialog changes.
draw-mark-hook (id)
This hook is called before a mark is drawn. If the hook returns #t, the mark is not drawn. mark-sync-color in snd-motif.scm uses this hook to draw sync'd marks in some other color than the current mark-color.
draw-mix-hook (id old-x old-y x y)
This hook is called before a mix tag is drawn. If the hook returns either #t or a list, the mix tag is not drawn by Snd (the assumption is that the hook function drew something). old-x and old-y are the previous mix tag positions (in case you're using draw-mix-hook to draw your own mix tag as in musglyphs.scm). x and y give the current position. If the hook returns a list, its two elements (integers) are treated as the mix's tag x and y locations for subsequent mouse click hit detection.
drop-hook (name)
This hook is called each time Snd receives a drag-and-drop event, passing the hook functions the dropped filename. If the hook returns #t, the file is not opened by Snd. Normally if you drag a file icon to the menubar, Snd opens it as if you had called open-sound. If you drag the icon to a particular channel, Snd mixes it at the mouse location in that channel. To get Snd to mix the dragged file even from the menubar:
(hook-push drop-hook
(lambda (hook)
(mix (hook 'name))
(set! (hook 'result) #t))) ; return #t = we already dealt with the drop
during-open-hook (fd name reason)
This hook is called after a file is opened, but before its data has been read. 'reason' is an integer indicating why this file is being opened:
0: reopen a temporarily closed file (internal to Snd — normally invisible) 1: sound-open, File:open etc — the normal path to open a sound 2: copy reader — another internal case; this happens if a sound is played and edited at the same time 3: insert sound (File:Insert etc) 4: re-read after an edit (file changed, etc — an invisible editing case) 5: open temp file after an edit (another invisible editing case) 6: mix sound (File:Mix etc)
So, to restrict the hook action to the normal case where Snd is opening a file for the first time, check that 'reason' is 1, or perhaps 1, 3, or 6 (these read the external form of the data).
effects-hook ()
effects-hook is a convenience hook for the effects dialogs.
enved-hook (envelope point x y reason)
Each time a breakpoint is changed in the envelope editor, this hook is called; if it returns a list, that list defines the new envelope, otherwise the breakpoint is moved (but not beyond the neighboring breakpoint), leaving other points untouched. The kind of change that triggered the hook callback is indicated by the argument 'reason'. It can be enved-move-point, enved-delete-point, or enved-add-point. This hook makes it possible to define attack and decay portions in the envelope editor, or use functions such as stretch-envelope from env.scm:
(hook-push enved-hook
(lambda (hook)
(let ((env (hook 'envelope))
(pt (* 2 (hook 'point)))
(x (hook 'x))
(y (hook 'y))
(reason (hook 'reason)))
(if (and (= reason enved-move-point)
(> x 0.0)
(< x (envelope-last-x env)))
(let ((new-env (stretch-envelope env (env pt) x)))
(set! (new-env (+ pt 1)) y)
(set! (hook 'result) new-env))))))
In Forth/Ruby, if there are several functions on the hook, each gets the envelope result of the preceding function.
exit-hook ()
This hook is called upon exit.
graph-hook (snd chn y0 y1)
This hook is called each time a graph is updated or redisplayed. If it returns #t, the display is not updated. See examp.scm for many examples. If you want to add your own graphics to the display, use after-graph-hook.
(hook-push graph-hook
(let ((+documentation+ "set the dot size depending on the number of samples being displayed"))
(lambda (hook)
(let* ((snd (hook 'snd))
(chn (hook 'chn))
(dots (- (right-sample snd chn) (left-sample snd chn))))
(set! (dot-size snd chn)
(cond ((assoc dots '((100 . 1) (50 . 3) (25 . 5)) >) => cdr)
(else 8)))))))
help-hook (subject help-string)
This hook is called from snd-help with the current help subject and default help-string. Say we want the index.scm procedure html called any time snd-help is called (from C-? for example):
(hook-push help-hook (lambda (hook) (html (hook 'subject))))
initial-graph-hook (snd chn duration)
This hook is called the first time a given channel is displayed (when the sound is first opened). If the hook returns a list, the list's contents are interpreted as:
(list x0 x1 y0 y1 label ymin ymax)
(all trailing values are optional), where these numbers set the initial x and y axis limits and the x axis label. The default (an empty hook) is equivalent to:
(hook-push initial-graph-hook
(lambda (hook)
(set! (hook 'result) (list 0.0 0.1 -1.0 1.0 "time" -1.0 1.0))))
The 'duration' argument is the total length in seconds of the displayed portion of the channel, so to cause the entire sound to be displayed initially:
(hook-push initial-graph-hook
(lambda (hook)
(set! (hook 'result) (list 0.0 (hook 'duration)))))
To get other the data limits (rather than the default y axis limits of -1.0 to 1.0), you can use mus-sound-maxamp, but if that sound's maxamp isn't already known, it can require a long process of reading the file. The following hook procedure uses the maxamp data if it is already available or if the file is short:
(hook-push initial-graph-hook
(lambda (hook)
(let ((snd (hook 'snd))
(chn (hook 'chn))
(dur (hook 'duration)))
(if (or (mus-sound-maxamp-exists? (file-name snd))
(< (framples snd chn) 10000000))
(let* ((amp-vals (mus-sound-maxamp (file-name snd)))
(max-val (max 1.0 (amp-vals (+ (* chn 2) 1)))))
;; max amp data is list: (sample value sample value ...)
(set! (hook 'result) (list 0.0 dur (- max-val) max-val))) ; these are the new y-axis limits
(set! (hook 'result) (list 0.0 dur -1.0 1.0)))))) ; max amp unknown, so use defaults
key-press-hook (snd chn key state)
This hook is called upon key press while the mouse is in the lisp graph (the third graph, to the right of the time and fft graphs). If it returns #t, the key press is not passed to the main handler. 'state' refers to the control, meta, and shift keys. start-enveloping in enved.scm uses this hook to add C-g and C-. support to the channel-specific envelope editors.
lisp-graph-hook (snd chn)
This hook is called just before the lisp graph is updated or redisplayed (see display-db). If it returns a list of pixels (xm style), they are used in order by the list of graphs, rather than Snd's default colors. If it returns a thunk, that function is called rather than the standard graph routine:
(hook-push lisp-graph-hook
(lambda (hook)
(let ((snd (hook 'snd))
(chn (hook 'chn)))
(set! (hook 'result)
(lambda ()
(draw-string "hi"
(x->position 0.5 snd chn lisp-graph)
(y->position 0.0 snd chn lisp-graph)
snd chn))))))
For a fancy example, see display-bark-fft in dsp.scm.
listener-click-hook (position)
This hook is called when a click occurs in the listener; the 'position' argument is the position in the text (a character number) where the click occurred.
mark-click-hook (id)
This hook is called when a mark is clicked; return #t to squelch the default status area mark identification.
(hook-push mark-click-hook
(lambda (hook)
(let ((n (hook 'id)))
(if (not (defined? 'mark-properties)) (load "marks.scm"))
(info-dialog "Mark Help"
(format #f "Mark ~A~A:~% sample: ~D = ~,3F secs~A~A"
n
(let ((name (mark-name n)))
(if (> (string-length name) 0)
(format #f " (~S)" name)
""))
(mark-sample n)
(* 1.0 (/ (mark-sample n) (srate (car (mark-home n)))))
(if (zero? (mark-sync n))
""
(format #f "~% sync: ~A" (mark-sync n)))
(let ((props (mark-properties n)))
(if (pair? props)
(format #f "~% properties: '~A" props)
""))))
(set! (hook 'result) #t))))
mark-drag-hook (id)
This hook is called when a mark is dragged. If it returns #t, the mark position is not reflected in the status area.
(define (report-mark-location id)
;; print current mark location in status area
(let ((samp (mark-sample id))
(sndchn (mark-home id)))
(status-report
(format #f "mark ~A: sample: ~D (~,3F) ~A[~D]: ~,3F"
id samp
(exact->inexact (/ samp (srate (car sndchn))))
(short-file-name (car sndchn))
(cadr sndchn)
(sample samp (car sndchn) (cadr sndchn))))))
(hook-push mark-drag-hook
(lambda (hook)
(report-mark-location (hook 'id))
(set! (hook 'result) #t)))
mark-hook (mark snd chn reason)
This hook is called when a mark is added, deleted, or moved (but not while moving). 'reason' can be 0: add, 1: delete, 2: move (via set! mark-sample), 3: delete all marks, 4: release (after drag). In the "release" case, the hook is called upon button release before any edits (control-drag of mark) or sorting (simple drag), and if the mark-sync is not 0, the hook is called on each syncd mark.
(define (snap-mark-to-beat)
;; when a mark is dragged, its end position is always on a beat
(hook-push mark-hook
(lambda (hook)
(let ((mrk (hook 'id))
(snd (hook 'snd))
(chn (hook 'chn))
(reason (hook 'reason)))
(let ((mark-release 4))
(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 (floor (/ (* beat sr) bps)))
(higher (floor (/ (* (+ 1 beat) sr) bps))))
(set! (mark-sample mrk) (if (< (- samp lower) (- higher samp))
lower
higher)))))))))
mix-click-hook (id)
This hook is called when a mix tag is clicked (when the double-arrow is displayed over the tag); return #t to omit the default action which is to start the Mix dialog with the clicked mix. One example is mix-click-info in mix.scm. Here's an example that sets a mix's amps to 0 if you click it (see mix-click-sets-amp in mix.scm for a fancier version):
(hook-push mix-click-hook
(lambda (hook)
(set! (mix-amp (hook 'id)) 0.0)
(set! (hook 'result) #t)))
mix-drag-hook (id x y)
This hook is called when a mix is dragged.
(hook-push mix-drag-hook
(lambda (hook)
(status-report
(format #f "mix ~A at ~D: ~,3F"
(hook 'id)
(mix-position (hook 'id))
(exact->inexact (/ (mix-position (hook 'id)) (srate)))))))
A neat example is to set up an empty sound with a 1.0 in sample 0, mix in a float-vector containing one element of 0.5, then set up this mix-drag-hook:
(hook-push mix-drag-hook
(lambda (hook)
(update-transform-graph)))
and turn on the FFT graph. As you drag the mix, you can see the spectral effect of that moving value as a comb filter.
mix-release-hook (id samples)
This hook is called after a mix has been dragged by the mouse to a new position. 'samples' is the number of samples moved during the drag. If the hook returns #t, the final position of the mix is hook's responsibility. See snap-mix-to-beat in mix.scm.
mouse-click-hook (snd chn button state x y axis)
This hook is called upon a mouse button release or click (with various exceptions). If its function returns #t, the click is ignored by Snd.
(define (click-to-center snd chn x axis)
;; if mouse click in time domain graph, set cursor as normally, but also center the window
(and (= axis time-graph)
(let ((samp (floor (* (srate snd) (position->x x snd chn)))))
(set! (cursor snd chn) samp)
(set! (right-sample snd chn)
(- samp (floor (* .5 (- (left-sample snd chn) (right-sample snd chn))))))
(update-time-graph)
#t)))
(hook-push mouse-click-hook
(lambda (hook)
(set! (hook 'result) (click-to-center (hook 'snd) (hook 'chn) (hook 'x) (hook 'axis)))))
;;; this example disables button 2 -> insert selection
(hook-push mouse-click-hook
(lambda (hook)
(set! (hook 'result)
(and (= (hook 'axis) time-graph)
(= (hook 'button) 2)))))
The mouse scroll wheel is sometimes reported as buttons 4 and 5; in the next example, turning the wheel zooms the graph in or out:
(hook-push mouse-click-hook
(lambda (hook)
(let ((button (hook 'button))
(axis (hook 'axis)))
(if (and (= axis time-graph)
(memv button '(4 5))) ; mouse scroll wheel
(let ((midpoint (* 0.5 (apply + (x-bounds))))
(dur (/ (framples) (srate)))
(range (if (= button 4)
(* -0.25 (apply - (x-bounds))) ; zoom in
(abs (apply - (x-bounds)))))) ; zoom out
(set! (x-bounds) (list (max 0.0 (- midpoint range))
(min dur (+ midpoint range)))))))))
Here is a Forth example:
mouse-click-hook lambda: <{ snd chn button state x y axis -- }>
axis time-graph = if
$" freq: %.3f" '( snd chn #f cursor snd chn spot-freq ) string-format
snd #f status-report
else
#f
then
; add-hook!
mouse-drag-hook (snd chn button state x y)
This hook is called when the mouse is dragged within the lisp graph (see enved.scm).
mouse-enter-graph-hook (snd chn)
This hook is called when the mouse enters a channel's drawing area (graph pane).
(hook-push mouse-enter-graph-hook
(lambda (hook)
(snd-print (format #f "~A[~A]" (short-file-name (hook 'snd)) (hook 'chn)))))
mouse-enter-label-hook (type position label)
This hook is called when the mouse enters a file viewer or region label. The 'type' is 1 for view files list, and 2 for regions. The 'position' is the scrolled list position of the label. The label itself is 'label'. We can use the finfo procedure in examp.scm to popup file info as follows:
(hook-push mouse-enter-label-hook
(lambda (hook)
(if (not (= (hook 'type) 2))
(info-dialog (hook 'label) (finfo (hook 'label))))))
See also files-popup-info in nb.scm.
mouse-enter-listener-hook (widget)
This hook is called when the mouse enters the listener pane. This hook, along with the parallel graph hook makes it possible to set up Snd to behave internally like a window manager with pointer focus. That is, to ensure that the pane under the mouse is the one that receives keyboard input, we can define the following hook procedures:
(hook-push mouse-enter-graph-hook
(lambda (hook)
(if (sound? (hook 'snd))
(focus-widget (car (channel-widgets (hook 'snd) (hook 'chn)))))))
(hook-push mouse-enter-listener-hook
(lambda (hook)
(focus-widget (hook 'widget))))
mouse-enter-text-hook (widget)
This hook is called when the mouse enters a text widget (this is the third of the pointer focus hooks).
(hook-push mouse-enter-text-hook
(lambda (hook)
(focus-widget (hook 'widget))))
mouse-leave-graph-hook (snd chn)
This hook is called when the mouse leaves a channel's drawing area (graph pane).
mouse-leave-label-hook (type position name)
This hook is called when the mouse exits one of the labels covered by mouse-enter-label-hook. See nb.scm.
mouse-leave-listener-hook (widget)
This hook is called when the mouse leaves the listener pane.
mouse-leave-text-hook (widget)
This hook is called when the mouse leaves a text widget.
mouse-press-hook (snd chn button state x y)
This hook is called upon a mouse button press within the lisp graph (see enved.scm). The 'x' and 'y' values are relative to the lisp graph axis (as if the raw mouse pixel position was passed through position->x and position->y).
mus-error-hook (type message)
This hook is called upon mus-error. If it returns #t, Snd ignores the error (it assumes you've handled it via the hook). Both mus_error and mus_print run this hook; in the mus_print case, the 'type' is mus-no-error (0). You can redirect mus_print output from stderr (the default) to stdout via:
(hook-push mus-error-hook
(lambda (hook)
(if (= (hook 'type) 0)
(begin
(display (hook 'message))
(set! (hook 'result) #t)))))
To decode the 'type' argument, see mus-error-type->string.
name-click-hook (snd)
This hook is called when the sound name is clicked (in the label in the status area region of the sound's pane). If the function returns #t, the usual highly informative status area babbling is squelched.
(hook-push name-click-hook
(lambda (hook) ; toggle read-only
(set! (read-only (hook 'snd)) (not (read-only (hook 'snd))))
(set! (hook 'result) #t)))
new-sound-hook (name)
This hook is called whenever a new sound file is being created. sound-let in ws.scm uses this hook to keep track of newly created temporary sounds so that it can delete them once they are no longer needed.
new-widget-hook (widget)
This hook is called each time a dialog or a new set of channel or sound widgets is created. This is used in misc.scm (paint-all) to make sure all newly created widgets have the same background pixmaps.
open-hook (name)
This hook is called before a sound file is opened. If the function returns #t, or the sound is not readable (bad header, etc) the file is not opened and any corresponding after-open-hook functions are not called. If it returns a string (a filename), that file is opened instead of the original one.
(hook-push open-hook
(lambda (hook)
(let ((filename (hook 'name)))
(if (and (= (mus-sound-header-type filename) mus-raw)
;; check for "OggS" first word, if found, translate to something Snd can read
(call-with-input-file filename
(lambda (fd)
(equal? (read-string 4 fd) "OggS"))))
(let ((aufile (string-append filename ".au")))
(if (file-exists? aufile) (delete-file aufile))
(system (format #f "ogg123 -d au -f ~A ~A" aufile filename))
(set! (hook 'result) aufile)))))) ; now open-sound will read the new .au file
open-raw-sound-hook (name state)
This hook is called each time open-sound encounters a headerless file. Its result can be a list describing the raw file's attributes (thereby bypassing the Raw File Dialog and so on): (list chans srate sample-type data-location data-length) where trailing elements can be omitted ('data-location' defaults to 0, and 'data-length' defaults to the file length in bytes). In Ruby and Forth, if there is more than one function on the hook list, functions after the first get the on-going list result as the 'state (the empty list is the default).
(hook-push open-raw-sound-hook
(lambda (hook)
(set! (hook 'result) (list 1 44100 mus-lshort))))
Return () to accept all the current raw header defaults; return #f to fallback on the Raw File Dialog. The raw header defaults are stereo, 44100 Hz, big endian short data; these values can be changed in the Raw File Dialog, by calling open-raw-sound with explicit arguments, or via mus-header-raw-defaults. If the hook returns #t, open-sound returns without opening the file.
orientation-hook ()
This hook is called whenever one of the variables associated with the orientation dialog changes.
output-comment-hook (comment)
This hook is called in the Save-As dialog to set the default output comment value. 'str' is the current sound's comment. If there is more than one hook function, each function's result is passed as input to the next function in the list.
(hook-push output-comment-hook
(lambda (hook) ; append a time-stamp
(set! (hook 'result)
(string-append (hook 'comment)
": written "
(strftime "%a %d-%b-%Y %H:%M %Z" (localtime (current-time)))))))
;; in Ruby: format("%s: written %s", comment, Time.new.localtime.strftime("%d-%b %H:%M %Z"))
play-hook (size)
This hook is called each time a buffer is about to be filled for the DAC. The buffer size is 'size'. See enved.scm and marks.scm.
read-hook (text)
This hook is called each time a line is typed into the listener (it is triggered by the carriage return).
save-hook (snd name)
This hook is called each time a sound ('snd') 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 save-sound-as). See autosave.scm.
save-state-hook (name)
This hook is called each time the save-state mechanism is about to create a new temporary file to save some edit history sample data; that is, each channel's edit history data is saved in a separate temporary file, and this hook provides a way to specify the name of that file. 'name' is the temporary file name that will be used unless the hook function returns a different one (as a string). This hook provides a way to keep track of which files are used in a given saved state batch, so that later cleanup is easier to manage.
select-channel-hook (snd chn)
This hook is called when a channel is selected (after the sound has been selected). The function arguments are the sound's index and the channel number.
select-sound-hook (snd)
This hook is called when a sound is selected. The argument is the about-to-be-selected sound.
snd-error-hook (message)
This hook is called upon snd-error. If the listener is closed, it is also called upon any Scheme, Ruby, or Forth error. If it returns #t, Snd flushes the error (it assumes you've dealt with it via the hook).
(hook-push snd-error-hook
(lambda (hook)
(play "bong.snd")))
snd-warning-hook (message)
This hook is called upon snd-warning. If it returns #t, Snd flushes the warning (it assumes you've reported it via the hook).
(define without-warnings
(lambda (thunk)
(let ((no-warning (lambda (hook) (set! (hook 'result) #t))))
(hook-push snd-warning-hook no-warning)
(thunk)
(hook-remove snd-warning-hook no-warning))))
start-playing-hook (snd)
This hook is called when a sound is about to be played. If its function returns #t, Snd does not play. We can use this hook to replace "play" with "play selection" if the selection is active:
(hook-push start-playing-hook
(lambda (hook)
(if (and (selection?)
(selection-member? (hook 'snd)))
(begin
(play (selection))
(set! (hook 'result) #t))))) ; there's a selection so don't play the entire sound
start-playing-selection-hook ()
This hook is called when the selection is about to be played. If its function returns #t, Snd does not play the selection.
stop-playing-hook (snd)
This hook is called when a sound finishes playing. stop-playing-hook may be called more often than start-playing-hook.
stop-playing-selection-hook ()
This hook is called when the selection finishes playing.
update-hook (snd)
update-hook is called just before a sound is updated ("update" means the sound is re-read from the disk, flushing the current version; this is useful if you overwrite a sound file with some other program, while viewing it in Snd). The update process can be triggered by a variety of situations, not just by update-sound. The hook is passed the sound's index. If it returns #t, the update is cancelled (this is not recommended!); if it returns a procedure of one argument, that procedure is called upon completion of the update operation; its argument is the (possibly different) sound. Snd tries to maintain the index across the update, but if you change the number of channels the newly updated sound may have a different index. add-mark-pane in snd-motif.scm uses the returned procedure to make sure the mark pane is reactivated right away when a sound is updated. The basic idea is:
(hook-push update-hook
(lambda (hook)
(set! (hook 'result)
(lambda (updated-snd) ; this code executed when update is complete
(snd-print "ok!")))))
I use update-hook to make sure the y axis bounds reflect the new maxamp, if it is greater than 1.0:
(hook-push update-hook
(lambda (hook)
(let ((old-snd (hook 'snd))) ; (hook 'snd) is the sound about to be updated
(set! (hook 'result)
(lambda (snd)
(do ((i 0 (+ i 1)))
((= i (channels snd)))
(let ((mx (maxamp snd i)))
(if (> mx 1.0)
(set! (y-bounds snd i) (list (- mx) mx))
(if (and (> (cadr (y-bounds old-snd)) 1.0) ; previous (pre-update) version was > 1.0
(<= mx 1.0)) ; but current is not, so reset axes
(set! (y-bounds snd i) (list -1.0 1.0)))))))))))
view-files-select-hook (dialog name) [Motif only]
This hook is called each time a file is selected in a View Files dialog's files list.
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. In Ruby and Forth the functions on these hooks are thunks — they should take no arguments. edit-hook is called just before any attempt to edit the channel's data; if it returns #t, the edit is cancelled. So,
Scheme: (hook-push (edit-hook hook) (lambda (hook) (set! (hook 'result) #t)))
Ruby: edit_hook(snd, chn).add_hook!(\"stop-edit\") do | | true end
Forth: snd chn edit-hook lambda: <{ }> #t ; add-hook!
halts any attempt to edit the data; this is even more restrictive than setting the read-only flag because the latter only refuses to overwrite the current data. undo-hook is called just after any undo, redo, or revert that affects the channel. after-edit-hook is called after an edit, but before after-graph-hook (add-mark-pane in snd-motif.scm uses this hook to update a mark list after each edit so that the displayed mark positions are correct). You can use edit-hook to set up protected portions of the edit history:
(define* (protect snd chn)
(let ((edit-pos (edit-position snd chn))
(hook (edit-hook snd chn)))
(set! (hook-functions hook)
(list
(lambda ()
(let ((val (< (edit-position snd chn) edit-pos)))
(if val (status-report "protected"))
(set! (hook 'result) val)))))))
(define* (unprotect snd chn)
(set! (hook-functions (edit-hook snd chn)) ()))
enved.scm uses several of these hooks to implement an envelope editor in lisp. add-mark-pane in snd-motif.scm uses them to make sure the mark list reflects the current edit history location. See also autosave.scm. It is possible for after-edit-hook to be called more often that edit-hook, or vice-versa; edit-hook may be called more than once on a given attempt to edit; if a long computation is required Snd may check edit-hook ahead of time to avoid unnecessary work.
Snd presents its various data structures as a list of sounds, each with a list of channels, each with lists of edits, marks, and mixes. The sound data itself is accessed through a variety of structures and functions, each aimed at a particular kind of use. The accessors from lowest level up are: samplers (one sample at a time iterators) and frample-readers (a "frample" is a multichannel sample), channel-at-a-time blocks (float-vectors, map-channel, etc), multichannel blocks (map-sound, etc), a few historical leftovers that follow the "sync" field (scale-to, etc), and finally the top-level operations such as save-sound-as (these are used in the File menu, etc). In the following sections, I'll start with the lowest level and work upwards, more or less. But before launching into samplers, I need to explain a few things about the following documentation.
Each sound is an object in Snd, and has an associated index. To refer to that sound, you can use either the object or the index. In the argument lists below, 'snd' as an argument refers to either the sound object or its index. It normally 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), (cursor 0 0), and (cursor (integer->sound 0)) all refer to the same thing. If you want to refer to the currently selected sound explicitly, use selected-sound.
In many cases, the 'snd', 'chn', and 'reg' arguments
can be #t which
means "all"; if 'snd' is #t, all sounds are included.
(expand-control #t)
returns a list of the current
control panel expansion settings of all sounds, and
(set! (transform-graph? #t #t) #t)
turns on the fft display in all channels of all sounds.
When an error occurs, the function throws a tag such as 'no-such-sound, 'no-active-selection, etc. All the functions that take sound and channel args ('snd chn' below) can return the errors 'no-such-sound and 'no-such-channel; all the mix-related functions can return 'no-such-mix; all the region-related functions can return 'no-such-region; all selection-oriented functions can return 'no-active-selection. To reduce clutter, I'll omit mention of these below.
The simplest data access function is sample which returns the sample at a given position in a sound's channel. This simplicity, however, comes at a price in computation: if the desired sample is not in Snd's in-core (already loaded) view of the data, it has to go get the sample, which can sometimes require that it open, read, and close a sound file. The result is that sample can bring your code to a grinding halt. There are two alternatives, leaving aside the scanning and mapping functions mentioned below. One involves keeping the buffer of data around explicitly (channel->float-vector), and the other involves the use of a special object known as a sampler. The sampler returns the next sample in its sound each time it is called; this kind of access is sometimes called an "enumerator" (Ruby) or perhaps "iterator". The buffer approach (channel->float-vector in expsrc) is better if you're jumping around in the data, the sample-by-sample approach if you're treating the data as a sequence of samples. To get a sampler, you create a reader (via make-sampler) giving it the start position, the sound and channel to read, and the initial read direction, then get data via read-sample (which remembers the read direction passed to make-sampler), or next-sample (read forward) and previous-sample (read backward); when done, you can close the reader with free-sampler, but it's usually not necessary; the garbage collector will take care of it if you forget (but, sigh, the GC can be dilatory at times).
There is a similar set of functions giving access to the mix data. make-mix-sampler returns a mix reader for the desired mix, mix-sampler? returns #t if its argument in a mix sampler, and read-mix-sample returns the next sample (before it is mixed into the output).
copy-sampler obj
copy-sampler returns a copy of 'obj' which can be any kind of sampler.
free-sampler obj
free-sampler releases the sampler 'obj'. In most cases, you don't need to call this function because the garbage collector handles the sampler object, but it doesn't hurt anything (but don't try to use a sampler after you've freed it!). If you're using zillions of samplers, sometimes freeing the samplers explicitly can reduce demands on memory.
make-mix-sampler mix (beg 0)
make-mix-sampler creates a mix-sampler reading 'mix' starting (in the mix input) at 'beg'. See mix->float-vector in mix.scm.
make-region-sampler reg start chn (dir 1)
make-region-sampler creates a sampler reading channel 'chn' of the region 'reg' starting at sample 'start', and reading forward if 'dir' is 1, backwards if 'dir' is -1. It is not safe to assume that this reader will return zeros beyond the region boundaries.
make-sampler start snd chn dir edpos
make-sampler creates a sampler reading the given channel starting at sample 'start' with initial read direction 'dir' (1=forward, -1=backward). 'edpos' is the edit history position to read; it defaults to the current edit.
> (open-sound "oboe.snd") #<sound 0> > (define reader (make-sampler 1000)) reader > reader #<sampler: oboe.snd[0: 0] from 1000, at 1000, forward> > (read-sample reader) 0.0328369140625 > (sample 1000) 0.0328369140625 > (next-sample reader) 0.0347900390625 > (sample 1001) 0.0347900390625 > (sampler-home reader) (#<sound 0> 0) > (sampler-position reader) 1002
One use of 'edpos' is to get the difference between two edits:
(define snd-diff
(lambda () ;assume mono, get diff between current state and previous
(let* ((index (selected-sound))
(edit-pos (edit-position index))
(previous-edit (make-sampler 0 0 index 1 (- edit-pos 1))))
(lambda (x)
(- x (read-sample previous-edit)) #f))))
(map-channel (snd-diff))
Once the reader has been set up to read at a given edit position, subsequent edits won't affect it. One sequence that takes advantage of this is: make-sampler, scale-by 0, then run an overlap-add process on the data from before the scaling.
'snd' can be a filename (a string); in this way a sampler can read external sounds without going to the trouble of loading them into Snd.
(define reader (make-sampler 100 "oboe.snd"))
'snd' also can be a mix or region. make-sampler is probably the most useful function in Snd; there are lots of examples in the Scheme, Ruby, and Forth files.
mix-sampler? obj
mix-sampler? returns #t if 'obj' is a mix-sampler.
next-sample obj
next-sample returns the next sample (reading forward) read by the sampler 'obj'.
previous-sample obj
previous-sample returns the previous sample in the stream read by the sampler 'obj'.
read-mix-sample obj
read-mix-sample returns the next sample read by the mix-sampler 'obj'.
read-region-sample obj
read-region-sample returns the next sample read by the region-sampler 'obj'.
(define* (region->float-vector reg (chn 0))
(cond ((not (region? reg))
(error 'no-such-region (list "region->float-vector" reg)))
((< chn (channels reg))
(let ((reader (make-region-sampler 0 reg chn))
(len (region-framples reg)))
(do ((data (make-float-vector len))
(i 0 (+ i 1)))
((= i len) data)
(set! (data i) (reader)))))
(else
(error 'no-such-channel (list "region->float-vector" reg chn)))))
read-sample obj
read-sample returns the next sample read by the sampler 'obj', reading in the direction set by make-sampler.
read-sample-with-direction obj dir
read-sample-with-direction returns the next sample read by the sampler 'obj', reading in the direction set by 'dir' (1 = forward, -1 = backward). This combination of next-sample and previous-sample is intended mainly for src.
region-sampler? obj
region-sampler? returns #t if 'obj' is a region sampler.
sampler-at-end? obj
sampler-at-end? returns #t if the sampler 'obj' (any kind of reader) is at the end of the sound (or whatever it is reading), and hence is returning 0.0 each time it is called. When the last "real" sample is returned, the at-end? flag is still false; when it becomes true, the sampler returns a 0.0 sample. See locate-zero in examp.scm, or linear-src-channel in dsp.scm.
sampler-home obj
sampler-home returns information describing the source of the data the sampler 'obj' is reading. if 'obj' is a sound sampler, it returns a list with the sound and channel number associated with 'obj'. If 'obj' is a mix reader, it returns the mix. Finally, if 'obj' is a region reader, it returns a list with the region.
sampler-position obj
sampler-position returns the current (sample-wise) location of the sampler 'obj' (any kind of reader).
sampler? obj
sampler? returns #t if 'obj' is a sampler.
If your extension language supports it, the read-sample functions can be omitted: (reader) is the same as (read-sample reader).
There is a Snd-specific CLM-style generator that redirects CLM instrument input (via in-any, ina, etc) to Snd data, snd->sample.
make-snd->sample snd
make-snd->sample creates a Snd data reader for use with CLM's in-any, file->sample, etc.
snd->sample gen frample chan
snd->sample gets the next sample from the data accessed by 'gen', similar to file->sample. If *reverb* is a snd->sample generator, for example, ina and file->sample actually call snd->sample.
snd->sample? obj
snd->sample? returns #t if 'obj' is a snd->sample generator.
These are arrays of floats. In s7, use "float-vector", and in Forth and Ruby use "vct".
list->float-vector lst list->vct lst
return a new float-vector with elements of list 'lst' (equivalent to the float-vector function).
make-float-vector len (initial-element 0.0) make-vct len (initial-element 0.0)
make-flo