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 (my typing is
in this color and Snd's responses
are in this color):
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, 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.
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.
Now when Snd is told to exit, it checks before-exit-hook, runs
unsaved-edits?, and if the latter returns #t, if prints
a worried message in the minibuffer, and refuses to
exit. Similar hooks customize actions such as closing
a sound (close-hook), clicking a mark (mark-click-hook),
pressing a key (key-press-hook), and so on.
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:
The variables are presented as a special kind of function, rather than as bare variables, mainly
to ensure that Snd's response to the assignment is immediate.
The statement (set! (auto-resize) #f)
can be placed in your ~/.snd initialization file
to make it the default setting for your version of Snd, or placed
in a separate file of Scheme code and loaded at any time via the load
function.
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?.
When some user-interface action takes place, code is called that responds to that action;
these functions are sometimes called callbacks; the variable that holds a list of such
callbacks is known as a hook.
A hook provides
a way to customize user-interface
actions.
The hook itself is list of functions. The function add-hook! adds a function to a hook's
list, remove-hook! removes a function, and reset-hook! clears out the list.
For example, the hook that is checked when you click the sound's name in the minibuffer is
name-click-hook. We can cause that action to print "hi":
If there is more than one function attached to a hook, some of the hooks
"or" the functions together (marked [or] below); that is they
run through the list of functions, and if any function returns something other than #f, the
hook invocation eventually returns the last such non-#f value. A few hooks are "cascade"
hooks; that is, each function gets the result of the previous function, and
the final function's value is returned.
In the other
cases ("progn", the name coming from Common Lisp), the result returned by the hook is the result of the last function in the list.
Whatever the hook combination choice, all the functions on the hook list are run
on each invocation. There are a variety of hook-related functions in hooks.scm.
There are several basic actions that involve a bunch of hooks. Here is a schematic view of
some of these sequences.
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):
These hooks are extremely easy to add; if there's some user-interface action
you'd like to specialize in some way, send me a note.
hooks.scm has snd-hooks and reset-all-hooks, as well as other
useful hook-related functions.
In the following list of hooks, the arguments after the hook name refer to the arguments to the functions invoked by
the hook. That is, after-apply-controls-hook (snd)
means that the functions on the
after-apply-controls-hook list each take one argument, a sound.
If the argument list is followed by some
indication such as "[or]", that means the various hook function values are or-d together.
after-apply-controls-hook (snd)
|
|
This hook is called when apply-controls finishes.
add-amp-controls in snd-motif.scm uses this hook to
reset any added amplitude sliders to 1.0.
|
|
after-graph-hook (snd chn)
|
|
This hook is called after a graph is updated or redisplayed; see display-samps-in-red,
draw-smpte-label in snd-motif.scm, or add-comment.
This is the hook to use when adding your own finishing touches to the display; if added earlier they risk
being erased by Snd as it redraws graphs.
|
|
after-lisp-graph-hook (snd chn)
|
|
This hook is called after a "lisp" graph is updated or redisplayed. The lisp-graph-hook functions
are called before the actual graph is displayed, so if you want to add to a graph in some way, you need to
use after-lisp-graph-hook.
display-bark-fft in dsp.scm uses it to draw the x axis labels and
ticks for various frequency scales.
|
|
after-open-hook (snd)
|
|
This hook is called just before a newly opened sound's window is displayed.
This provides a way to set various sound-specific defaults.
For example, the following causes Snd to default to locally
sync'd channels (that is, each sound's channels are sync'd
together but are independent of any other sound), united channels (all chans in one graph),
and filled graphs (not line segments or dots, etc):
See also C-x b support in examp.scm,
remember-sound-state in extensions.scm,
enved.scm, and various examples in snd-motif.scm.
|
|
after-save-as-hook (index filename from-dialog)
|
|
This hook is called after File:Save as. See emacs-style-save-as in snd7.scm
which closes the current sound and opens the newly created one to mimic Emacs.
|
|
after-save-state-hook (filename)
|
|
This hook is called after Snd has saved its state (save-state). 'filename' is the (otherwise complete) saved state
program. See ws-save-state in ws.scm
or remember-sound-state in extensions.scm. Both use this sequence:
(lambda (filename)
(let ((fd (open filename (logior O_RDWR O_APPEND)))) ; open to write at the end
(format fd "~%~%;;; save-state stuff here ~%")
...
(close fd)))
|
|
after-transform-hook (snd chn scaler)
|
|
This hook is called just after an FFT (or spectrum) is calculated.
|
|
bad-header-hook (filename) [or]
|
|
This hook is called if a file has a bogus-looking header (that is,
a header with what appear to be bad values such as a negative number of channels).
If a hook function returns #t, Snd does not try to open the file.
(add-hook! bad-header-hook (lambda (n) #t)) ; don't open bogus-looking files
If no header is found, open-raw-sound-hook is invoked instead ("raw" = "headerless").
|
|
before-close-hook (snd) [or]
|
|
This hook is called when a file is about to be closed. If a hook function returns #t, the file is not closed (see
check-for-unsaved-edits in extensions.scm).
|
|
before-exit-hook () [or]
|
|
This hook is called upon a request to exit Snd.
If a hook function returns #t, Snd does not exit. This can be used to check
for unsaved edits (see above or extensions.scm: unsaved-edits?).
|
|
before-save-as-hook (index filename selection srate header-type data-format comment) [or]
|
|
This hook is called before save-sound-as or File:Save as.
If a hook function returns something other than #f, the
save is not performed. This hook provides a way to do last minute fixups (srate conversion for example)
just before a sound is saved. The arguments to the hook function describe the requested attributes of the saved sound;
'index' is the to-be-saved sound's index; 'filename' is the output file's name; 'selection' is #t if
we're saving the selection.
(add-hook! before-save-as-hook
(lambda (index filename selection sr type format comment)
(if (not (= sr (srate index)))
(let ((chns (channels index)))
(do ((i 0 (+ 1 i)))
((= i chns))
(src-channel (exact->inexact (/ (srate index) sr)) 0 #f index i))
(save-sound-as filename index :header-type type :data-format format :srate sr :comment comment)
;; hook won't be invoked recursively
(do ((i 0 (+ 1 i)))
((= i chns))
(undo 1 index i))
#t) ; tell Snd that the sound is already saved
#f)))
|
|
|
before-save-state-hook (filename) [or]
|
|
This hook is called before Snd saves its state (save-state). 'filename' is the saved state
file. If the hook functions return #t, the save state file is opened in append mode (rather than create/truncate),
so you can write preliminary stuff via this hook, then instruct Snd not to clobber it during the save process.
(add-hook! before-save-state-hook
(lambda (name)
(with-output-to-file name
(lambda ()
(display (format #f ";this comment will be at the top of the saved state file.~%~%"))
#t))))
|
|
|
before-transform-hook (snd chn) [progn]
|
|
This hook is called just before an FFT (or spectrum) is calculated. If a hook function returns
an integer, that value is used as the starting point (sample number) of the fft. Normally,
the fft starts from the left window edge. To have it start at mid-window:
The following
somewhat brute-force code shows a way to have the fft reflect the position
of a moving mark:
|
|
clip-hook (clipping-value) [progn]
|
|
This hook is called whenever a sample is about to be clipped while writing out a sound file.
The hook function can return the new value.
|
|
close-hook (snd)
|
|
This hook is called when a file is closed (before the actual close, so the index 'snd' is still valid).
(add-hook! close-hook
(lambda (snd)
(system "sndplay wood16.wav")))
|
| |
$close_hook.add_hook!("play") do |snd|
system("aplay wood16.wav")
end
|
|
close-hook is used in autosave.scm,
the C-x b support in examp.scm,
remember-sound-state in extensions.scm,
and many other places.
|
|
color-hook () [progn]
|
|
This hook is called whenever one of the variables associated with the color dialog changes.
See start-waterfall in snd-gl.scm.
|
|
dac-hook (data) [progn]
|
|
This hook is called just before data is sent to DAC; 'data' is a sound-data object.
See with-level-meters in snd-motif.scm.
|
|
draw-mark-hook (mark) [progn]
|
|
This hook is called before a mark is drawn (in XOR mode except in cairo). If the hook function returns #t, the mark is not drawn.
mark-sync-color
in snd-motif.scm uses this hook to draw sync'd marks in some other color than the current mark-color.
|
|
draw-mix-hook (mix old-x old-y x y) [progn]
|
|
This hook is called before a mix tag is drawn. If the hook function returns either #t or a list, the mix tag is not drawn by Snd (the
assumption is that the hook function drew something). old-x and old-y are the previous mix tag positions (in case
you're using draw-mix-hook to draw your own mix tag as in musglyphs.scm). x and y give the current position.
If the hook function returns a list, its two elements (integers) are treated as the mix's tag x and y locations
for subsequent mouse click hit detection.
|
|
drop-hook (filename) [or]
|
|
This hook is called each time Snd receives a drag-and-drop event, passing the hook functions the dropped filename.
If the hook functions return #t, the file is not opened by Snd. Normally if you drag a file icon to the menubar,
Snd opens it as if you had called open-sound. If you drag the icon to a particular channel,
Snd mixes it at the mouse location in that channel. To get Snd to
mix the dragged file even from the menubar:
(add-hook! drop-hook (lambda (filename) (mix filename) #t)) ; return #t = we already dealt with the drop
snd-motif.scm has examples that add a drop callback to an arbitrary widget, or
change an existing callback (to pass the sound and channel number to the drop callback function, bypassing drop-hook).
|
|
during-open-hook (fd name reason)
|
|
This hook is called after file is opened, but before data has been read.
This provides an opportunity to set sndlib prescaling values:
The prescaling affects only sound data made up of floats or doubles. 'reason' is an integer indicating why this file is being opened:
0: reopen a temporarily closed file (internal to Snd — normally invisible)
1: sound-open, File:open etc — the normal path to open a sound
2: copy reader — another internal case; this happens if a sound is played and edited at the same time
3: insert sound (File:Insert etc)
4: re-read after an edit (file changed, etc — an invisible editing case)
5: open temp file after an edit (another invisible editing case)
6: mix sound (File:Mix etc)
So, to restrict the hook action to the normal case where Snd is opening a file for the first time,
check that 'reason' is 1, or perhaps 1, 3, or 6 (these read the external form of the data).
|
|
enved-hook (env pt new-x new-y reason) [cascade]
|
|
Each time a breakpoint is changed in the envelope editor, this hook
is called; if it returns a list, that list defines the new envelope,
otherwise the breakpoint is moved (but not beyond the neighboring
breakpoint), leaving other points untouched. The kind of change that triggered the hook callback
is indicated by the argument 'reason'. It can be enved-move-point , enved-delete-point ,
or enved-add-point . This hook makes it possible to define attack
and decay portions in the envelope editor, or use functions such as
stretch-envelope from env.scm:
(add-hook! enved-hook
(lambda (env pt x y reason)
(if (= reason enved-move-point)
(if (and (> x 0.0) (< x (envelope-last-x env))) ; from env.scm
(let* ((old-x (list-ref env (* pt 2)))
(new-env (stretch-envelope env old-x x)))
(list-set! new-env (+ (* pt 2) 1) y)
new-env)
env)
#f)))
|
If there are several functions on the hook, each gets the envelope
result of the preceding function (if a function returns #f, the envelope
is not changed). A math-type would call this a "function composition"
method combination; a filter-type would say "cascade";
|
|
exit-hook ()
|
|
This hook is called upon exit.
|
|
graph-hook (snd chn y0 y1) [or]
|
|
This hook is called each time a graph is updated or redisplayed.
If its hook functions return #t, the display is not updated.
See examp.scm for many examples. If you want to add your own graphics to the display, use after-graph-hook.
(add-hook! graph-hook
(lambda (snd chn y0 y1)
"set the dot size depending on the number of samples being displayed"
(let ((dots (- (right-sample snd chn) (left-sample snd chn))))
(if (> dots 100)
(set! (dot-size snd chn) 1)
(if (> dots 50)
(set! (dot-size snd chn) 2)
(if (> dots 25)
(set! (dot-size snd chn) 3)
(set! (dot-size snd chn) 5))))
#f)))
|
|
|
help-hook (subject help-string) [cascade]
|
|
This hook is called from snd-help with the current help subject and default help-string.
Say we want the index.scm
procedure html called any time snd-help is called (from C-? for example):
(add-hook! help-hook (lambda (subject help) (html subject) #f))
If there is more than one hook function, each function's result is passed as input to the next function.
|
|
initial-graph-hook (snd chn dur) [or]
|
|
This hook is called the first time a given channel is displayed (when the sound is first opened).
If the hook function returns a list, the list's contents are interpreted as:
(list x0 x1 y0 y1 label ymin ymax)
(all trailing values are optional), where these numbers set the
initial x and y axis limits and the x axis label.
The default (an empty hook) is equivalent to:
(add-hook! initial-graph-hook (lambda (snd chn dur) (list 0.0 0.1 -1.0 1.0 "time" -1.0 1.0)))
The 'dur' argument is the total length in seconds of the displayed portion of the channel, so to cause the
entire sound to be displayed initially:
(add-hook! initial-graph-hook (lambda (snd chn dur) (list 0.0 dur)))
To get other the data limits (rather than the default y axis limits of -1.0 to 1.0), you can use mus-sound-maxamp,
but if that sound's maxamp isn't already known, it can require a long process of reading the file. The following hook procedure
uses the maxamp data if it is already available or the file is short:
(add-hook! initial-graph-hook
(lambda (snd chn dur)
(if (or (mus-sound-maxamp-exists? (file-name snd))
(< (frames snd chn) 10000000))
(let* ((amp-vals (mus-sound-maxamp (file-name snd)))
(max-val (max 1.0 (list-ref amp-vals (+ (* chn 2) 1)))))
;; max amp data is list: (sample value sample value ...)
(list 0.0 dur (- max-val) max-val)) ; these are the new y-axis limits
(list 0.0 dur -1.0 1.0)))) ; max amp unknown, so use defaults
|
|
|
key-press-hook (snd chn key state) [or]
|
|
This hook is called upon key press while the mouse is in the lisp graph (the third graph,
to the right of the time and fft graphs).
If its function returns #t, the key press is not passed to the main handler.
'state' refers to the control, meta, and shift keys.
start-enveloping in enved.scm uses this hook to add C-g and C-. support to the
channel-specific envelope editors.
|
|
lisp-graph-hook (snd chn) [progn]
|
|
This hook is called just before the lisp graph is updated or redisplayed (see display-db).
If its function returns a list of pixels (xm style), these are used in order by the list of graphs (if any), rather than Snd's default set
(this makes it possible to use different colors for the various graphs).
If it returns a function (of no arguments), that function is called rather than the standard graph routine:
For a fancy example, see display-bark-fft in dsp.scm.
|
|
listener-click-hook (textpos)
|
|
This hook is called when a click occurs in the listener; the 'textpos' argument is the position in the text
(a character number) where the click occurred.
See click-for-listener-help in draw.scm.
|
|
mark-click-hook (mark) [progn]
|
|
This hook is called when a mark is clicked; return #t to squelch the default minibuffer mark identification. The following
hook function is used in with-marked-sound in ws.scm to display arbitrary info about a mark.
|
|
mark-drag-hook (mark)
|
|
This hook is called when a mark is dragged.
|
|
mark-drag-triangle-hook (mark x time dragged-before) [progn]
|
|
This hook is called when a mark play triangle is dragged. The smoothness of the response to the drag motion is
largely determined by dac-size.
'dragged-before' is #f when the drag starts and #t thereafter. 'x' is the mouse x location in the current
graph. 'time' is the uninterpreted (graphics toolkit) time at which the drag event was reported. If the hook function returns #t,
Snd takes no further action. To set up to play, then interpret the motion yourself, return #f on the first call,
and #t thereafter:
(let ((first-x 0))
(add-hook! mark-drag-triangle-hook
(lambda (id x time dragged-before)
(if (not dragged-before)
(set! first-x x)
(set! (speed-control) (/ (- x first-x) 16.0)))
dragged-before)))
|
|
|
mark-hook (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
(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 (floor (/ (* beat sr) bps)))
(higher (floor (/ (* (+ 1 beat) sr) bps))))
(set! (mark-sample mrk)
(if (< (- samp lower) (- higher samp))
lower
higher))))))))
|
|
|
mix-click-hook (mix) [progn]
|
|
This hook is called when a mix tag is clicked; 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):
|
|
mix-drag-hook (mix x y)
|
|
This hook is called when a mix is dragged.
A neat example is to set up an empty sound with a 1.0 in sample 0, mix in a vct containing one element of 0.5, then set
up this mix-drag-hook:
(add-hook! mix-drag-hook (lambda (id x y) (update-transform-graph)))
and turn on the FFT graph. As you drag the mix, you can see the spectral effect of that
moving value as a comb filter.
|
|
mix-release-hook (mix samps) [progn]
|
|
This hook is called after a mix has been dragged by the mouse to a new position.
'samps' is the number of samples moved during the drag. If its function returns #t, the final position
of the mix is
hook's responsibility. See snap-mix-to-beat in mix.scm.
|
|
mouse-click-hook (snd chn button state x y axis) [or]
|
|
This hook is called upon a mouse button release or click (with various exceptions). If its function returns #t, the click is ignored by Snd.
(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 (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)
#f))
(add-hook! mouse-click-hook click-to-center)
;;; this example disables button 2 -> insert selection
(add-hook! mouse-click-hook
(lambda (snd chn button state x y axis)
(and (= axis time-graph) (= button 2))))
|
The mouse scroll wheel is sometimes reported as buttons 4 and 5; in the next example,
turning the wheel zooms the graph in or out:
(add-hook! mouse-click-hook
(lambda (snd chn button state x y axis)
(if (and (= axis time-graph)
(or (= button 4) (= button 5))) ; mouse scroll wheel
(let ((midpoint (* 0.5 (apply + (x-bounds))))
(dur (/ (frames) (srate)))
(range (if (= button 4)
(* -0.25 (apply - (x-bounds))) ; zoom in
(abs (apply - (x-bounds)))))) ; zoom out
(set! (x-bounds) (list (max 0.0 (- midpoint range))
(min dur (+ midpoint range))))))
#f))
|
Here is a Forth example:
mouse-click-hook lambda: <{ snd chn button state x y axis -- }>
axis time-graph = if
$" freq: %.3f" '( snd chn #f cursor snd chn spot-freq ) string-format
snd #f report-in-minibuffer
else
#f
then
; add-hook!
|
|
|
mouse-drag-hook (snd chn button state x y)
|
|
This hook is called when the mouse is dragged within the lisp graph (see enved.scm or rtio.scm).
|
|
mouse-enter-graph-hook (snd chn)
|
|
This hook is called when the mouse enters a channel's drawing area (graph pane).
|
|
mouse-enter-label-hook (type position label)
|
|
This hook is called when the mouse enters a file viewer or region label.
The 'type' is 1 for view files list, and 2 for regions.
The 'position' is the scrolled list position of the label.
The label itself is 'label'. We can use the finfo procedure in examp.scm
to popup file info as follows:
(add-hook! mouse-enter-label-hook
(lambda (type position name)
(if (not (= type 2))
(info-dialog name (finfo name)))))
|
See also files-popup-info in nb.scm.
|
|
mouse-enter-listener-hook (widget)
|
|
This hook is called when the mouse enters the listener pane. This hook, along with the parallel graph hook
makes it possible to set up Snd to behave internally like a window manager with pointer focus. That is, to
ensure that the pane under the mouse is the one that receives keyboard input, we can define the following
hook procedures:
I much prefer this style of operation.
|
|
mouse-enter-text-hook (widget)
|
|
This hook is called when the mouse enters a text widget (this is the third of the pointer focus hooks).
|
|
mouse-leave-graph-hook (snd chn)
|
|
This hook is called when the mouse leaves a channel's drawing area (graph pane).
|
|
mouse-leave-label-hook (type position name)
|
|
This hook is called when the mouse exits one of the labels covered by mouse-enter-label-hook.
See nb.scm.
|
|
mouse-leave-listener-hook (widget)
|
|
This hook is called when the mouse leaves the listener pane.
|
|
mouse-leave-text-hook (widget)
|
|
This hook is called when the mouse leaves a text widget.
|
|
mouse-press-hook (snd chn button state x y)
|
|
This hook is called upon a mouse button press within the lisp graph (see enved.scm). The 'x' and 'y' values are
relative to the lisp graph axis (as if the raw mouse pixel position was passed through
position->x and position->y).
|
|
mus-error-hook (error-type error-message) [or]
|
|
This hook is called upon mus-error.
If its functions return #t, Snd ignores the error (it assumes you've handled it via the hook).
This hook is used in play-sound in play.scm to flush an error message
that the Snd ALSA support code generates (or used to generate).
Both mus_error and mus_print run this hook; in the mus_print case, the 'type' is mus-no-error (0).
You can redirect mus_print output from stderr (the default) to stdout via:
(add-hook! mus-error-hook
(lambda (typ msg)
(and (= typ 0) ; it's mus_print, not mus_error
(display msg)))) ; display returns some non-#f result, I assume
|
To decode the 'error-type' argument, see mus-error-type->string.
|
|
name-click-hook (snd) [or]
|
|
This hook is called when the sound name is clicked (in the label in the minibuffer region of the sound's pane).
If the function returns #t, the usual highly informative minibuffer babbling is squelched.
|
|
new-sound-hook (filename)
|
|
This hook is called whenever a new sound file is being created. sound-let in ws.scm uses
this hook to keep track of newly created temporary sounds so that it can delete them once they are no longer needed.
|
|
new-widget-hook (widget)
|
|
This hook is called each time a dialog or a new set of channel or sound widgets is created.
This is used in misc.scm (paint-all) to
make sure all newly created widgets have the same background pixmaps.
|
|
open-hook (filename) [or]
|
|
This hook is called before a sound file is opened.
If the function returns #t, or the sound is not readable (bad header, etc) the file is not opened
and any corresponding after-open-hook functions are not called.
If it returns a string (a filename), that file is opened instead of the original one.
(add-hook! open-hook
(lambda (filename)
(if (= (mus-sound-header-type filename) mus-raw)
;; check for "OggS" first word, if found, translate to something Snd can read
(if (call-with-input-file filename
(lambda (fd)
(and (char=? (read-char fd) #\O)
(char=? (read-char fd) #\g)
(char=? (read-char fd) #\g)
(char=? (read-char fd) #\S))))
(let ((aufile (string-append filename ".au")))
(if (file-exists? aufile) (delete-file aufile))
(system (format #f "ogg123 -d au -f ~A ~A" aufile filename))
aufile) ; now open-sound will read the new .au file
#f)
#f)))
|
See also open-buffer in examp.scm.
|
|
open-raw-sound-hook (filename current-choices) [cascade]
|
|
This hook is called each time open-sound encounters a headerless file.
Its result can be a list describing the raw file's attributes (thereby bypassing the Raw File Dialog and so on):
(list chans srate data-format data-location data-length) where trailing elements can
be omitted ('data-location' defaults to 0, and 'data-length' defaults to the file length in bytes).
If there is more than one function on the hook list, functions after the first get the
on-going list result (if any) as the 'current-choices' argument (the empty list is the default).
(add-hook! open-raw-sound-hook (lambda (file choices) (list 1 44100 mus-lshort)))
Return '() to accept all the current raw header defaults; return #f to fallback on the Raw File Dialog.
The raw header defaults are stereo, 44100 Hz, big endian short data; these values can be changed in the
Raw File Dialog, by calling open-raw-sound with explicit arguments,
or via mus-header-raw-defaults.
If the hook function returns #t, the open-sound returns without opening.
|
|
optimization-hook (message)
|
|
This hook is called each time the optimizer hits something it can't handle; 'message' tries to give some information about the situation.
(add-hook! optimization-hook (lambda (n) (display (format #f "~A~%" n))))
Normally, if the optimizer fails for some reason, it falls back silently on the Scheme evaluator, so
the code runs slower. This hook gives you a way to find out why the optimizer gave up.
|
|
orientation-hook () [progn]
|
|
This hook is called whenever one of the variables associated with the orientation dialog changes.
See start-waterfall in snd-gl.scm.
|
|
output-comment-hook (str) [cascade]
|
|
This hook is called in the Save-As dialog to set the default output comment value. 'str' is the current sound's comment.
If there is more than one hook function, each function's result is passed as input to the next function in the list.
(add-hook! output-comment-hook
(lambda (str) ; append a time-stamp
(string-append str ": written " (strftime "%a %d-%b-%Y %H:%M %Z" (localtime (current-time))))))
;; in Ruby: format("%s: written %s", str, Time.new.localtime.strftime("%d-%b %H:%M %Z"))
|
|
|
output-name-hook (current-name) [progn]
|
|
This hook is called in the New File dialog. If it returns a filename, that name is presented in the New File dialog,
making it slightly easier to set default output names.
(let ((file-ctr -1))
(add-hook! output-name-hook
(lambda (ignored-name)
(set! file-ctr (+ file-ctr 1))
(format #f "~A-~D.snd" ; make new file name based on date and file-ctr: "Jun-01-23.snd"
(strftime "%b-%d" (localtime (current-time)))
file-ctr))))
|
|
|
peak-env-hook (snd chn)
|
|
This hook is called upon completion of a new peak envelope.
|
|
play-hook (samps)
|
|
This hook is called each time a buffer is about to be
filled for the DAC. The buffer size is 'samps'.
See enved.scm and marks.scm.
|
|
print-hook (text) [progn]
|
|
This hook is called each time some Snd-generated response ('text') is about to be appended to the listener.
If the function returns some non-#f result, Snd assumes you've sent the text out yourself, as well as any needed prompt.
The prompt is important! Snd uses it to find the current form to evaluate, so if your print hook function
forgets to include it, you can end up with a comatose listener. To get out of this state, include
the prompt by hand (i.e. type in the shell that Snd started in, ">(reset-hook! print-hook)").
This is intended to make it possible to
distinguish Snd responses from user-typing, or add arbitrarily fancy prompts, but both should be handled
in some other way — I should rewrite this part of Snd.
(add-hook! print-hook
(lambda (msg)
(if (char=? (string-ref msg 0) #\newline)
(snd-print msg)
(snd-print (format #f "~A~%[~A]~%~A" ;need newline just before listener-prompt
msg
(strftime "%d-%b %H:%M %Z" (localtime (current-time)))
(listener-prompt))))))
|
|
|
read-hook (text) [or]
|
|
This hook is called each time a line is typed into the listener (it is triggered by the carriage return).
|
| |
save-hook (snd name) [or]
|
|
This hook is called each time a sound ('snd') is about to be saved.
If its function returns #t, the file is not saved. 'name' is #f unless
the file is being saved under a new name (as in save-sound-as).
See autosave.scm.
|
|
save-state-hook (temp-filename)
|
|
This hook is called each time the save-state
mechanism is about to create a new temporary file to save some edit history sample data; that is,
each channel's edit history data is saved in a separate temporary file, and this hook provides
a way to specify the name of that file.
'temp-filename' is the temporary file name that will be used unless
the hook function returns a different one (as a string). This hook provides a way to
keep track of which files are used in a given saved state batch, so
that later cleanup is easier to manage.
|
|
select-channel-hook (snd chn)
|
|
This hook is called when a channel is selected (after the sound has been selected).
The function arguments are the sound's index and the channel number.
select-channel-hook is used in play-between-marks
to keep the loop bounds up-to-date.
|
|
select-sound-hook (snd)
|
|
This hook is called when a sound is selected. The argument is the about-to-be-selected sound.
|
|
snd-error-hook (error-message) [or]
|
|
This hook is called upon snd-error. If the listener is closed, it is also called upon any Scheme, Ruby, or Forth error.
If it returns #t, Snd flushes the error (it assumes you've
dealt with it via the hook).
(add-hook! snd-error-hook
(lambda (msg)
(play "bong.snd") ; or if xm is loaded, (XBell (XtDisplay (cadr (main-widgets))) 10)
#f))
|
|
|
snd-warning-hook (warning-message) [or]
|
|
This hook is called upon snd-warning.
If it returns #t, Snd flushes the warning (it assumes you've
reported it via the hook).
(define without-warnings
(lambda (thunk)
(define no-warning (lambda (msg) #t))
(add-hook! snd-warning-hook no-warning)
(thunk)
(remove-hook! snd-warning-hook no-warning)))
|
|
|
start-hook (filename) [or]
|
|
This hook is called when Snd starts.
If its function returns #t, Snd exits immediately.
Say we are so annoyed with Snd's really very fine file browser that we want
Snd to exit back to the shell if its file argument is not
found (this code has to be in the ~/.snd init file):
(add-hook! start-hook
(lambda (file)
(if (not (file-exists? file))
(begin
(display file) (display " does not exist")
#t))))
|
|
|
start-playing-hook (snd) [or]
|
|
This hook is called when a sound is about to be played.
If its function returns #t, Snd does not play.
We can use this hook to replace "play" with "play selection" if the
selection is active:
(add-hook! start-playing-hook
(lambda (snd)
(if (and (selection?)
(selection-member? snd))
(begin
(play (selection))
#t) ; there's a selection so don't play the entire sound
#f))) ; no selection, so go ahead and play it all
|
See also report-mark-names in marks.scm.
|
|
start-playing-selection-hook () [or]
|
|
This hook is called when the selection is about to be played.
If its function returns #t, Snd does not play the selection.
|
|
stop-dac-hook ()
|
|
This hook is called when Snd stops playing and turns off the DAC, normally upon mus-audio-close.
It is used by with-level-meters to start draining away the red bubble in the VU meter.
|
|
stop-playing-hook (snd)
|
|
This hook is called when a sound finishes playing. stop-playing-hook may be called more often than start-playing-hook.
|
|
stop-playing-selection-hook () [progn]
|
|
This hook is called when the selection finishes playing.
|
|
time-graph-hook (snd chn) [progn]
|
|
This hook is called just before each channel's time domain graph is updated if channel-style is
channels-combined (that is, when all the channels are superimposed in one graph).
If its function returns a pixel, that color is used to draw that channel's data.
|
|
update-hook (snd) [or]
|
|
update-hook is called just before a sound is updated ("update" means the sound is re-read from the disk, flushing the current version; this
is useful if you overwrite a sound file with some other program, while viewing it in Snd).
The update process can be triggered by a variety of situations, not just by update-sound.
The hook is passed the sound's index. If its function returns #t, the update is cancelled (this is not
recommended!); if it returns a procedure of one argument, that procedure is called upon
completion of the update operation; its argument is the (possibly different) sound.
Snd tries to maintain the index across the update, but if you change the number of channels
the newly updated sound may have a different index. add-mark-pane in snd-motif.scm uses
the returned procedure to make sure the mark pane is reactivated right away when a sound is updated. The basic idea is:
(add-hook! update-hook
(lambda (snd-about-to-be-updated)
;; this function called just before update
(lambda (updated-snd)
;; this code executed when update is complete
(snd-print "ok!"))))
|
I use update-hook to make sure the y axis bounds reflect the new maxamp, if it is greater than 1.0:
(add-hook! update-hook
(lambda (old-snd)
(lambda (snd)
(do ((i 0 (+ 1 i)))
((= i (channels snd)))
(let ((mx (maxamp snd i)))
(if (> mx 1.0)
(set! (y-bounds snd i) (list (- mx) mx))
(if (and (> (cadr (y-bounds snd)) 1.0) ; previous (pre-update) version was > 1.0
(<= mx 1.0)) ; but current is not, so reset axes
(set! (y-bounds snd i) (list -1.0 1.0)))))))))
|
Say we're editing a sound in several different windows, and want to save and re-apply any
edits we may have in the other windows when we save the sound from one window (all the others
then get the exploding bomb icon because their underlying data has changed):
(let ((eds #f))
(add-hook! update-hook
(lambda (cursnd) ; called before actual update -- save our edits
(set! eds (edit-list->function cursnd 0))
(lambda (newsnd) ; called after update -- reapply our edits
(if eds (eds newsnd 0))))))
|
Actually this only works if there are just two windows on the same (mono) sound. I suppose for
trickier cases, we could set up an association list with the old sound ("cursnd" above) and its edits,
but it's probably better to bind some keystroke to a function that performs the same operations.
|
|
view-files-select-hook (dialog filename)
|
|
This hook is called each time a file is selected in a View Files dialog's files list.
|
|
window-property-changed-hook (command) [or]
|
|
This hook is called when Snd sees a SND_COMMAND window property change.
If its function returns #t, the command is not evaluated. This is an internal debugging hook.
|
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 frame-readers (a "frame" is a multichannel sample),
channel-at-a-time blocks (vcts, map-channel, etc), multichannel blocks (sound-data objects, map-sound, etc),
a few historical leftovers that follow the "sync" field (scale-to, etc), and finally
the top-level operations such as save-sound-as (these are used in the File menu, etc).
In the following sections, I'll start with the lowest level and work upwards, more or less.
But before launching into 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.
In the function documentation here, optional arguments are brown, and optional keyword arguments are preceded by a colon.
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.
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.
Many of the Snd and CLM functions handle vectors (arrays) of data.
By defining a new vector type, named vct, and providing a package
of old-style array-processing calls upon that type, we can speed up many
operations by a factor of 30 — enough of a difference to warrant
the added complexity.
make-vct creates a new vct object. It is freed by the
garbage collector when it can't be referenced any further. To get
an element of a vct, use vct-ref; similarly vct-set!
sets an element. (vct's are applicable objects, so vct-ref can always be omitted, and set! can replace vct-set!).
Once created, a vct can be passed to a variety of built-in
functions:
Now our vct 'hi' has 100 -3.14's.
Many of the functions described below can take a vct as an argument;
there are also several functions that create and fill vcts with data:
There is one slightly
unusual function in this family: vct-map!.
This is a do-loop (or for-each) over a vct, calling some
function to get the values to assign into the vct. For example
In some cases (s7, Ruby) it's possible to access a vct's
elements with the syntax (v index)
, equivalent to (vct-ref v index)
.
In Ruby, vcts partake in the Enumerable and Comparable classes, and have a variety of
additional methods: map, each, <=>, etc. See vct.c and the Ruby documentation for a complete list.
Another Snd object containing sound samples is the sound-data object; it can be viewed either as an array of vcts,
each vct representing one channel's data, or as an array of frames, each frame holding a sample from
each channel at that position in the sound. frame.scm has a number of useful functions that use
sound-data objects.
As with vcts, sound-data objects partake in the Enumerable and Comparable
classes in Ruby.
mus-alsa-buffers
|
|
mus-alsa-buffers is the number of buffers ("periods") used in ALSA; you can also use the environment variable MUS_ALSA_BUFFERS.
The default setting is 3.
These ALSA variables only matter if you built Snd with the configure switch --with-alsa.
|
|
mus-alsa-buffer-size
|
|
mus-alsa-buffer-size is the buffer size used in ALSA. You can also use the environment variable MUS_ALSA_BUFFER_SIZE.
The defaut setting is 1024.
|
|
mus-alsa-device
|
|
This is the ALSA audio device; it defaults to "default".
The matching environment variable is MUS_ALSA_DEVICE. If the ALSA "default" device can't be found, we also look
for "plughw:0" and "hw:0". The "0" is apparently a card number or something. On my machine where the internal
sound card is worse than useless, I have an EMI 2|6 connected to a USB port. Its ALSA name seems to be "hw:1"
on a good day.
|
|
mus-alsa-capture-device
|
|
This is the ALSA capture (audio recording) device. The matching environment variable is MUS_ALSA_CAPTURE_DEVICE.
|
|
mus-alsa-playback-device
|
|
This is the ALSA audio playback device. The matching environment variable is MUS_ALSA_PLAYBACK_DEVICE.
|
|
mus-alsa-squelch-warning
|
|
Set mus-alsa-squelch-warning to #t to squelch warnings from ALSA about srate mismatches.
|
|
mus-audio-close line
|
|
mus-audio-close closes the audio port 'line' ('line' comes from either mus-audio-open-input
or mus-audio-open-output).
(let* ((audio-fd (mus-audio-open-output mus-audio-default 22050 1 mus-lshort 1024))
;; open DAC at srate 22050, 1 channel, 1024 bytes per buffer
(osc (make-oscil 440.0)) ; make a sine wave at 440 Hz
(data (make-sound-data 1 512))) ; output buffer (1 chan, 512 samples)
(do ((beg 0 (+ beg 512))) ; fill a buffer at a time
((> beg 22050)) ; stop playing after 1 second
(do ((i 0 (+ 1 i))) ; write each sample of the sinusoid
((= i 512)) ; until this buffer is full
(sound-data-set! data 0 i (* .1 (oscil osc)))) ; .1 amp
(mus-audio-write audio-fd data 512)) ; send a buffer to the DAC
(mus-audio-close audio-fd)) ; close the DAC
|
|
|
mus-audio-describe
|
|
mus-audio-describe returns a string describing the current audio hardware state.
|
|
mus-audio-open-input device srate chans format bufsize
|
|
mus-audio-open-input opens the audio input (recording) port 'device'. It returns -1 if the open failed.
|
|
mus-audio-open-output device srate chans format bufsize
|
|
mus-audio-open-output opens the audio output (playback) port 'device'. It returns -1 if the open failed.
|
|
mus-audio-read line sd frames
|
|
mus-audio-read reads 'frames' frames of data into the sound-data object 'sd' from the audio input port 'line'.
|
|
mus-audio-write line sd frames (start 0)
|
|
mus-audio-write writes 'frames' frames of data from the sound-data object 'sd' to the audio output port 'line'.
If 'start' is given, it sets where to start reading in the sound-data buffers.
(let ((sd (make-sound-data 1 44100)))
(with-sound (:output sd)
(fm-violin 0 1 440 .1)
(fm-violin .5 .1 660 .1))
(let* ((audio-fd (mus-audio-open-output mus-audio-default 44100 1 mus-lfloat (* 4 1024))))
(do ((i 0 (+ i 1024)))
((>= i 44100))
(mus-audio-write audio-fd sd 1024 i))
(mus-audio-close audio-fd)))
|
|
mus-bytes-per-sample data-format
|
|
mus-bytes-per-sample returns the number of bytes that 'data-format' uses to encode one sample of sound.
:(mus-bytes-per-sample mus-bdouble)
8
|
|
mus-clipping
|
|
mus-clipping is the default low-level clipping choice while accessing sound data.
Its default is #f which makes clipping very obvious (it will cause wrap-around).
If you're using the standard Snd file accessors, you probably want to use clipping, not this function.
This function refers to mus-sound-open-output and friends; the file-local version
is mus-file-clipping — surely we could make this more confusing!
See also clip-hook.
|
|
mus-data-format-name format
|
|
mus-data-format-name converts 'format' from an integer to an explanatory string, e.g. "16-bit big endian linear".
The sndlib data formats are: |
|
mus-bshort mus-lshort mus-mulaw mus-alaw mus-byte
mus-lfloat mus-bint mus-lint mus-b24int mus-l24int
mus-ubshort mus-ulshort mus-ubyte mus-bfloat mus-bdouble
mus-ldouble mus-unknown
There are also "unscaled" versions of the floating point types, and "normalized" versions of the integers.
|
|
mus-data-format->string format
|
|
mus-data-format->string converts 'format' from an integer to a string, e.g. "mus-mulaw".
:(mus-data-format->string mus-bdouble)
"mus-bdouble"
|
|
mus-error-type->string error
|
|
mus-error-type->string returns a brief string description of 'error'
(a mus-error return type). This is only useful in mus-error-hook, and it's not very useful even there.
|
|
mus-expand-filename name
|
|
mus-expand-filename fills out the filename 'name' to include its 'absolute' pathname;
that is, it replaces '~' with the current home directory,
and whatever else seems appropriate.
:(mus-expand-filename "oboe.snd")
"/home/bil/cl/oboe.snd"
|
|
mus-file-clipping fd
|
|
This is the clipping choice for the file referred to by 'fd'
(a file identifier as returned by mus-sound-open-input).
The default is #f which makes clipping very obvious (it will cause wrap-around).
See also clip-hook.
|
|
mus-file-prescaler fd
|
|
This is the prescaling value for reading data from the sndlib file descriptor 'fd'.
If you're reading float data that is extremely soft (i.e. peak amplitude
below .001), the transfer to integer form in sndlib (if you're using an integer internal sample format) can cause bits
to be lost, resulting in hiss. In this case set the prescaler for
the file to 1000.0 or so to get the data into a more normal
range. Since mus-file-prescaler must be set after opening
the sound file, but before trying to read any data, you need to use it in the context of during-open-hook.
The default prescaler value is mus-prescaler, normally 1.0.
|
|
mus-header-raw-defaults
|
|
mus-header-raw-defaults returns a list: '(srate chans data-format), the current raw header defaults. These can be
set:
(set! (mus-header-raw-defaults) (list 22050 4 mus-lint))
|
|
mus-header-type-name type
|
|
mus-header-type-name converts 'type', an integer, to a string, e.g. "AIFF". Some of the sndlib header types are:
|
|
mus-next mus-aifc mus-riff mus-rf64 mus-nist mus-raw mus-ircam mus-aiff mus-caff
mus-bicsf mus-soundfont mus-voc mus-svx mus-unsupported
This function doesn't set the header choices for output of recording; it just decodes a sndlib header type identifier.
:(mus-header-type-name (mus-sound-header-type "oboe.snd"))
"Sun/Next"
The default sound output header choice is default-output-header-type,
a sound file's
header type is mus-sound-header-type, the CLM (with-sound) header default
is *clm-header-type*, and an opened sound's header type is header-type.
|
|
mus-header-type->string type
|
|
mus-header-type->string converts 'type', an integer, to a string, e.g. "mus-aifc".
|
|
mus-oss-set-buffers num size
|
|
In Linux (OSS), this sets the number and size of the OSS fragments.
The default (as of 21-May-01) is to accept whatever OSS chooses: I believe this is normally
equivalent to (mus-oss-set-buffers 16 12) . This default makes the control panel controls very sluggish.
Snd used to call (mus-oss-set-buffers 4 12) as its default,
but this seems to cause trouble for a variety of new sound cards.
My initialization file includes (mus-oss-set-buffers 2 12) .
|
|
mus-prescaler
|
|
mus-prescaler is the global default prescaling value for reading data from a file (see mus-file-prescaler).
|
|
mus-sound-chans filename
|
|
This is the number of channels in 'filename'. This value can be set (as can the others like it mentioned below);
the assignment refers to the table of sound file data maintained by sndlib. The file itself is not touched, but any
subsequent reference to it in Snd will assume the new value. In the mus-sound-chans case, say we have a sound file
whose header claims it has 43 channels, but we know it only has 2:
(set! (mus-sound-chans "43chans.snd") 2)
tells Snd that it has 2 channels no matter what the header says.
|
|
mus-sound-close-input fd
|
|
This closes the sound file referred to by 'fd'; 'fd' is an integer returned by mus-sound-open-input.
These mus-sound-* file functions refer to a direct path to read and write sound files. Any such operation is beneath the
notice, so to speak, of Snd.
This function reads the first 32 samples of a file, returning the 30th in channel 0:
|
|
mus-sound-close-output fd bytes
|
|
This function closes the sound file 'fd', and updates its length indication, if any to be 'bytes' bytes.
If you didn't specify a data-format to mus-sound-open-output,
it defaulted to mus-out-format .
mus-out-format can
currently be either ints, floats, or doubles, depending on how you've configured Snd, so it's safest
to use (mus-bytes-per-sample mus-out-format)
for the number of bytes per sample.
|
|
mus-sound-comment filename
|
|
mus-sound-comment returns the comment in the header of the file 'filename'.
:(with-sound (:comment "this is a comment") (fm-violin 0 1 440 .1))
"test.snd"
:(mus-sound-comment "test.snd")
"this is a comment"
|
|
mus-sound-data-format filename
|
|
mus-sound-data-format returns the data format (e.g. mus-bshort ) of the file 'filename'.
:(mus-data-format->string (mus-sound-data-format "oboe.snd"))
"mus-bshort"
|
|
mus-sound-data-location filename
|
|
This is the location in bytes of the first sample in the file 'filename'.
|
|
mus-sound-datum-size filename
|
|
This returns the size in bytes of each sample in 'filename'.
It is equivalent to (mus-bytes-per-sample (mus-sound-data-format filename)) .
|
|
mus-sound-duration filename
|
|
This returns the duration in seconds of the sound data in the file 'filename'.
:(mus-sound-duration "oboe.snd")
2.30512475967407
|
|
mus-sound-forget filename
|
|
mus-sound-forget removes the file 'filename' from the sndlib sound cache.
|
|
mus-sound-frames filename
|
|
mus-sound-frames returns the number of frames of sound data in the file 'filename' according to its header
(this number is occasionally incorrect in mus-next headers).
|
|
mus-sound-header-type filename
|
|
This returns the header type (e.g. mus-aifc ) of the file 'filename'.
:(mus-header-type->string (mus-sound-header-type "oboe.snd"))
"mus-next"
|
|
mus-sound-length filename
|
|
mus-sound-length returns the number of bytes of sound data in the file 'filename'.
|
|
mus-sound-loop-info filename
|
|
This function refers to the "loop" info that is sometimes found in some headers (aifc, wav etc).
mark-loops in examp.scm uses mus-sound-loop-info to place a mark at each loop point.
:(mus-sound-loop-info "~/sf1/forest.aiff")
(24981 144332 0 0 60 0 1 0)
The loop info is a list of
up to 4 points, the first two (start, end = 24981 144332 above) refer to the sustain loop,
and the second two (0 0 above) refer to the release.
The 5th and 6th list entries are the base note and detune values (60 0 above).
For historical reasons, the 7th and 8th entries are the sustain and release modes (1 0 above).
The looper instrument uses this function to implement a sort of "freeze" function.
See also sound-loop-info.
|
|
mus-sound-mark-info filename
|
|
This function refers to the "mark" info that is sometimes found in aifc and aiff headers.
It returns a list of lists (or an empty list if there are no marks),
each inner list being (mark-id mark-position). The mark-id is a number that identifies it for
use with mus-sound-loop-info, and the mark-position is its sample number in the file.
Normally, this information is already included in the mus-sound-loop-info list:
:(mus-sound-mark-info "/home/bil/sf1/forest.aiff")
((4 1) (3 0) (2 144332) (1 24981))
:(mus-sound-loop-info "/home/bil/sf1/forest.aiff")
(24981 144332 0 0 60 0 1 0)
|
|
mus-sound-maxamp filename
|
|
mus-sound-maxamp returns a list of max amps and locations thereof. The corresponding set!
affects only the sndlib table of sound file info, not the sound file itself, as in all such cases.
:(mus-sound-maxamp "oboe.snd")
(24971 0.147247314453125)
;; oboe's maxamp is .147 first encountered at sample 24971
:(mus-sound-maxamp "2a.snd")
(933 0.0999755859375 2827 0.0999755859375)
;; 2a's maxamps are 0.1 in each channel at sample 933 in chan 0, 2827 in chan 1
|
|
mus-sound-maxamp-exists? filename
|
|
This function returns #t if the sound's maxamp data is available
in the sound cache; if it isn't, a call on mus-sound-maxamp has to open and read the data to get the maxamp.
:(mus-sound-maxamp-exists? "/home/bil/test/sound/away.snd")
#f
:(mus-sound-maxamp "/home/bil/test/sound/away.snd")
(14562264 0.463623046875 14557044 0.404571533203125)
:(mus-sound-maxamp-exists? "/home/bil/test/sound/away.snd")
#t
|
|
mus-sound-open-input filename
|
|
mus-sound-open-input opens the sound file 'filename' and returns an integer for use with
mus-sound-read and mus-sound-close-input.
|
|
mus-sound-open-output filename (srate 44100) (chans 1) data-format header-type comment
|
|
mus-sound-open-output creates a new sound file with the indicated attributes, and returns an integer
for use with mus-sound-write and mus-sound-close-output.
|
|
mus-sound-prune
|
|
mus-sound-prune removes all defunct (non-existent) files from the sound cache. This is primarily intended for internal testing (snd-test.scm).
|
|
mus-sound-read fd beg end chans sd
|
|
mus-sound-read reads data from the sound file 'fd', loading the sound-data object 'sd' from 'beg'
to 'end'.
|
|
mus-sound-reopen-output filename (chans 1) data-format header-type data-location
|
|
mus-sound-reopen-output reopens the sound file 'filename', ready to continue writing output.
|
|
mus-sound-report-cache file
|
|
This function prints the current sound header data table to the file given or stdout if none is specified.
|
|
mus-sound-samples filename
|
|
mus-sound-samples returns the number of samples in the sound file 'filename' according to its header.
:(mus-sound-samples "oboe.snd")
50828
|
|
mus-sound-seek-frame fd frame
|
|
mus-sound-seek-frame moves the input or output reading point to 'frame' in the sound file 'fd'.
|
|
mus-sound-srate filename
|
|
mus-sound-srate returns the sampling rate of 'filename'.
|
|
mus-sound-type-specifier filename
|
|
This is the original type indication of 'filename'. This is only useful in internal testing.
|
|
mus-sound-write fd beg end chans sd
|
|
mus-sound-write writes data from the sound-data object 'sd' to the sound file 'fd'.
|
|
mus-sound-write-date filename
|
|
This returns the sound's write date:
|
|
:(strftime "%d-%b %H:%M %Z" (localtime (mus-sound-write-date "oboe.snd")))
"18-Oct 06:56 PDT"
|
|
The following function uses the sndlib functions to mimic the 'info' popup menu option (see examp.scm for a version that uses format):
A mark is an object that refers to a particular sample.
Each mark has an associated sample number (mark-sample),
name (mark-name), and sync value (mark-sync).
See Marks in snd.html
for an overview and key bindings associated with marks.
See also the hooks section above for various mark-related hooks.
Other examples can be found in Dave Phillips' marks-menu.scm, snd-motif.scm (add-mark-pane),
edit-menu.scm (trim from mark, etc), examp.scm (move window to correspond to mark, looping).
Mixing operations have a lot of extra support built into Snd. In nearly every mixing function, you
can request a "mix tag" (or set that request globally via with-mix-tags).
If the mix operation is tagged, you can then operate on that data through a number of functions,
the Mix Dialog, various hooks, and various mouse-related actions.
A mix is an object that represents a channel (one channel in and one channel out) of a sound mix.
Various mixing functions create these objects (mix-vct for example). In the old days, mixes were identified
by integers, so for conversion you can use mix->integer and integer->mix.
Say we have a mix object stored in the variable "id":
This sets the mix's amplitude scaler to .5.
A region is a saved portion of sound data. Use the View:Region browser to inspect, edit, and save regions.
As regions are defined, the new ones are pushed on a stack, and if enough regions already
exist, old ones are pushed off (and deleted) to make room.
The selected portion can be chosen, independent of any region, by setting selection-position and selection-frames.
It's easy to extend the notion of a selection to an arbitrary list of sound portions:
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 and a channel number. When the function refers to a variable
that can be set locally on a sound (zero-pad, for example),
the 'snd' and 'chn' arguments can be #t, referring to all current sounds or all channels of a sound.
In cases where it makes sense, if the 'snd' argument is omitted, the
reference is to the global default value. So, (set! (amp-control-bounds) '(0.0 2.0))
sets the global amp control (slider) bounds to be between 0.0 and 2.0, whereas
(set! (amp-control-bounds snd) '(0.0 2.0))
sets it only for the sound referred to by 'snd'.
Many of the procedures also have an 'edpos' argument (standing for "edit position").
It always defaults to the current edit history position. If specified, it can be either an edit history position (to which
the operation is applied), the constant current-edit-position (the default), or a function
of two arguments, the sound and the channel number. The function should return the
desired edit history position. In most cases, you should only refer to edits in the past
(that is, 'edpos' should be less than or equal to the current edit-position); in a few
situations, you can make use of data in the "redo" section of the edit-history list, but
nothing is guaranteed.
add-player player start end edpos stop-proc out-chan
|
|
add-player adds 'player' to the play-list (see make-player).
If 'edpos' is given, play at that edit position.
'stop-proc' can be a procedure of one argument; it is called when the play process stops and passed
the reason the play is stopping; it will be 0 if the play completed normally (the other possibilities
are listed here, but they really aren't interesting).
The 'out-chan' argument is the audio output channel to send the data to; it defaults to
the channel number of the player's channel in the containing sound (that is, the default is to
send channel 1 data to channel 1 of the DAC, and so on).
See play-with-envs in enved.scm, play-syncd-marks in marks.scm, or start-dac in play.scm.
|
|
axis-info snd chn grf
|
|
axis-info returns a list describing the specified axis:
(list left-sample right-sample
x0 y0 x1 y1 x-min y-min x-max y-max
x0-position y0-position x1-position y1-position y-offset
xlabel ylabel new-peaks)
This can be
useful if you're drawing arbitrary figures in a graph. 'grf' defaults to
time-graph ; the other choices are transform-graph and lisp-graph .
'x0' is the time in seconds corresponding to the left-sample (the left edge of the graph).
Similarly 'y0' is the lower y axis limit as a sample value (i.e. -1.0).
'x-max' is the sound's duration in seconds ('x-min' is always 0.0).
The "positions" are pixel values, in drawing area coordinates; these give the position
of the graph in the drawing area. 'y-offset' refers to "united" graphs where
several channels share one drawing area. You can use it to translate mouse coordinates
to channel number in that situation.
For example, x->position
could be:
(define (x->position-1 x snd chn)
(let* ((axinfo (axis-info snd chn time-graph))
(x0 (list-ref axinfo 2))
(x1 (list-ref axinfo 4))
(axis-left (list-ref axinfo 10))
(axis-right (list-ref axinfo 12)))
(floor
(+ axis-left
(* (- x x0)
(/ (- axis-right axis-left)
(- x1 x0)))))))
|
Here's a key binding that uses axis-info to save every channel's graph position upon "Page Down",
then restore that state upon "Page Up":
(bind-key "Page_Down" 0
(lambda ()
(let ((last-page-state
(map (lambda (snd)
(let ((data (list snd (file-name snd))))
(do ((i 0 (+ 1 i)))
((= i (channels snd)) data)
(set! data (append data (list (cons i (axis-info snd i))))))))
(sounds))))
(bind-key "Page_Up" 0
(lambda ()
(if last-page-state
(for-each
(lambda (lst)
(let ((snd (list-ref lst 0))
(name (list-ref lst 1)))
(if (and (sound? snd)
(string=? (file-name snd) name))
(for-each
(lambda (chan-data)
(let ((chn (list-ref chan-data 0))
(x0 (list-ref chan-data 3))
(x1 (list-ref chan-data 5))
(y0 (list-ref chan-data 4))
(y1 (list-ref chan-data 6)))
(set! (x-bounds snd chn) (list x0 x1))
(set! (y-bounds snd chn) (list y0 y1))))
(cddr lst)))))
last-page-state)))))))
|
See also draw-smpte-label in snd-motif.scm.
|
|
beats-per-measure snd chn
|
|
The x axis labelling of the time domain waveform can be in measures
(x-axis-style = x-axis-in-measures ); this variable sets the number of beats per measure.
The default is 4.
|
|
beats-per-minute snd chn
|
|
The x axis labelling of the time domain waveform can be in beats
(x-axis-style = x-axis-in-beats ) or in measures
(x-axis-in-measures ); this variable sets the number of beats per minute.
The default is 60.0, making it the same as x-axis-in-seconds .
See snap-mark-to-beat, or snap-mix-to-beat.
|
|
bomb snd on
|
|
bomb displays an exploding bomb icon next to 'snd's' name (in the minibuffer area). Set 'on' to #f to erase the bomb. Each time bomb
is called, the bomb icon moves to the next image in its sequence (showing the bomb's fuse burning down),
restarting the sequence whenever it reaches the end. This icon is used when a sound and its underlying file
get out of sync somehow (auto-update).
(define show-bomb
(lambda (n speed)
(if (> n 0)
(begin
(bomb)
(in speed (lambda () (show-bomb (- n 1) speed))))
(bomb 0 #f))))
(show-bomb 15 200) ; there are 15 images in the sequence
|
|
|
channel-amp-envs file chan size peak-file-func work-proc-func
|
|
channel-amp-envs returns two vcts of length 'size' containing the peak-amp envelopes of the channel 'chan' of file 'file'.
'peak-file-func' (if any) is used to get the name of the associated peak-env 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, 'size' is an edit-position, and the current amp envs (if any) are returned.
The arguments to 'peak-file-func' are the file and the channel. If it returns a string, that is treated as the filename
to read to get the peak info. The arguments to 'work-proc-func' are the filename, the channel and the current peak.
make-sound-icon in make-sound-box in snd-motif.scm uses
this function to draw the little thumbnail graph for each sound icon.
|
|
channel-data snd chn
|
|
channel-data provides very low-level access to the data currently in the given channel's sample buffers.
It is used by the variable-display mechanism to show graphs
of variable values (normally in an instrument). channel-data only works with sounds returned
by make-variable-display, and only in a float-sample version of Snd (i.e. not one that was built with
the configure argument --without-float-samples). See make-variable-display in snd-motif.scm.
|
|
channel-properties snd chn
|
|
channel-properties is a property list associated with a channel. It is set to '() at the time a sound is opened, so
it provides a relatively simple way to save data about a channel which will automatically be erased when the channel is closed.
channel-property reads and writes this list.
Traditionally in Lisp, a property list has been treated as an association list. This is a list
of pairs (made by cons), each inner pair having a key as its first element, and the associated value as the second element.
The function assoc can be used to search the list for a given key's value; a new key-value pair can be
added with:
(cons (cons key value) a-list)
In Common Lisp, property lists have other properties, so to speak, but channel-properties (and
sound-properties) can be handled in any way you like.
See channel-sync in extensions.scm for a brief example; more
elaborate examples are in enved.scm (enved-envelope), or draw.scm (colored-samples and insert-envelope).
|
|
channel-property key snd chn
|
|
channel-property returns the value associated with 'key' in the given channel's
property list. To add or change a property, use set! with this procedure.
Scheme:
:(set! (channel-property 'info 0 0) "this is sound 0, first channel")
"this is sound 0, first channel"
:(channel-property 'info 0 0)
"this is sound 0, first channel"
Ruby:
>set_channel_property(:info, "this is info", 0, 0)
this is info
>channel_property(:info, 0, 0)
this is info
Forth:
>'info "this is info" 0 0 set-channel-property
'( '( 'info . this is info ) )
>'info 0 0 channel-property
this is info
The property list is convenient because the associated information goes away automatically
when the channel is closed, and the property lists are saved by save-state.
|
|
channel-style snd
|
|
channel-style reflects the value of the 'unite' button in multichannel files.
Possible values are channels-separate , channels-combined (the default), and channels-superimposed .
The following code sets the 'unite' button if the current sound has more than 4 channels:
|
|
channel->vct beg dur snd chn edpos
|
|
channel->vct returns a vct with the specified data. In Ruby, the "->" in a function name is translated to "2",
so the function call is:
v = channel2vct(0, 100)
(define* (selection->vct snd chn)
(if (selection-member? snd chn)
(channel->vct (selection-position snd chn)
(selection-frames snd chn)
snd chn)
(if (selection?)
(throw 'no-such-channel
(list "selection->vct"
(format #f "snd ~A channel ~D is not a member of the selection" snd chn)))
(throw 'no-active-selection (list "selection->vct")))))
|
See also mark-explode in marks.scm.
|
|
channels snd
chans snd
|
|
This function returns the number of channels in 'snd'. It can be set, but the result is a new
version of the underlying sound with the header changed to reflect the new number of channels.
That is, no new data is created, but the existing data is reapportioned to the new channels:
(set! (channels) 2) ; this is not undo-able (except by calling it again with the
original number of channels — the data is not touched).
|
|
clear-minibuffer snd
|
|
This clears the sound's minibuffer area (both the text and the error message widgets).
|
|
clm-channel clm-gen beg dur snd chn edpos overlap origin
|
|
clm-channel applies 'clm-gen' to the given channel starting
at sample 'beg' for 'dur' samples, and 'overlap' samples of 'ring time'.
This is used by some of the regularized functions, but it can also be used directly:
(define* (convolve-channel kernel nbeg ndur nsnd nchn nedpos)
(let* ((beg (or nbeg 0))
(snd (or nsnd (selected-sound) (car (sounds))))
(chn (or nchn (selected-channel)))
(dur (or ndur (- (frames snd chn) beg)))
(edpos (or nedpos current-edit-position))
(reader (make-sampler beg snd chn 1 edpos))
(cgen (make-convolve :filter kernel
:input (lambda (dir)
(read-sample reader)))))
(clm-channel cgen beg dur snd chn edpos)
(free-sampler reader)))
(define (difference) (clm-channel (make-two-zero 1 -1)))
(define (wobble) (clm-channel (make-ncos 50 3)))
(define (hold-nose) (clm-channel (make-ncos 1 3)))
(define (bad-reception) (clm-channel (make-ncos 10 5)))
|
|
|
close-sound snd
|
|
This closes 'snd' (the same as the File:Close menu item). To close all sounds:
(close-sound #t)
;; equivalent to:
(for-each close-sound (sounds))
Before the sound is actually closed, before-close-hook
is called, then close-hook,
then the sound is closed.
|
|
comment snd
|
|
This returns the sound's comment, if any; when a sound is opened, the comment is taken from the file's header
(the same as mus-sound-comment). If you set it, the header is not updated until the sound is saved.
If the new comment is the only change you want to make, you can save the new header via the Edit:Edit Header menu option.
|
|
convolve-with file amp snd chn edpos
|
|
This convolves the given channel (or the currently sync'd data)
with the data in the sound file 'file'. 'amp' is the resultant
peak amplitude (leave 'amp' unset, or set it to #f to get the
unnormalized result).
Convolve-with in conjunction with mix can provide high-quality reverb:
(define conrev
(lambda (impulse amp)
(convolve-with impulse amp)
(save-sound-as "reverb.snd") ;let mix scalers set reverb amount
(revert-sound)
(mix "reverb.snd")))
|
|
|
count-matches proc sample snd chn edpos
|
|
This returns how many samples satisfy the function 'proc'; 'proc' should
take one argument (the current sample value), and return #t for a hit. 'sample'
determines where to start the search.
Scheme: (count-matches (lambda (y) (> y .1)))
Ruby: count_matches(lambda do |y| y > 0.1 end)
Forth: lambda: <{ y }> y 0.1 f- f0< ; count-matches
count-matches is modelled after Emacs. It could be defined along these lines:
(define (count-matches proc)
(let ((count 0))
(scan-channel
(lambda (y)
(if (proc y) (set! count (+ count 1)))
#f))
count))
|
|
|
cursor snd chn edpos
|
|
This returns the cursor location (as a sample number; the first sample is numbered 0) in channel 'chn' of 'snd'.
(set! (cursor) 100) moves the cursor to sample 100. The cursor is somewhat similar to a
mark in that it moves if you delete or insert samples in front of it.
|
|
cursor-follows-play snd
|
|
This is #t if the cursor is following along in the sound as it plays. The new name of this variable
is with-tracking-cursor.
|
|
cursor-position snd chn
|
|
This gives the current cursor position as a list (x y).
These graph-relative values can be turned into axis-relative values with
position->x and position->y:
(position->x (car (cursor-position)))
;; equals:
(/ (cursor) (srate))
|
|
cursor-size snd chn
|
|
This gives the cursor size in pixels; it defaults to 15. (set! (cursor-size) 30) makes the cursor twice as big as usual.
|
|
cursor-style snd chn
|
|
The cursor style is cursor-cross , cursor-line , or a cursor-drawing function.
The default cursor shape is a "+" sign; the cursor-line is a vertical line.
As a function, cursor-style is a procedure of three arguments, the
sound, channel number, and a boolean that is true if the cursor is currently
tracking playback (a "tracking-cursor").
The procedure
should draw the cursor at the current cursor position using the
cursor-context.
Here is a simpler one that
replaces the normal "+" cursor with an "x":
(define (x-cursor snd chn ax)
(let* ((point (cursor-position))
(x (car point))
(y (cadr point))
(size (floor (/ (cursor-size) 2))))
(draw-line (- x size) (- y size) (+ x size) (+ y size) snd chn cursor-context)
(draw-line (- x size) (+ y size) (+ x size) (- y size) snd chn cursor-context)))
(set! (cursor-style) x-cursor)
|
|
|
data-format snd
|
|
This returns the sound's data format — the encoding used for the sound samples (e.g. mus-bshort).
The standard formats nowadays are mus-bshort (big-endian 16-bit integers), mus-bfloat
(32-bit big-endian floats), and mus-bint (32-bit big-endian integers), and the
corresponding little-endian versions: mus-lshort , mus-lfloat , and mus-lint .
If you're using an Intel-style PC, you're using a little-endian machine;
Old macs (PowerPC Macs) and Suns use big-endian (NeXT, SGI, and Atari also used it in the good old days). If you
write a Next file and use little-endian data, some programs other than Snd
may complain; similarly, RIFF wants little-endian and AIFC wants big-endian
data (both can handle the other kind, but most sound-related programs don't know
that). In the old days, when disk space was at a premium, 8-bit formats
were used a lot: mus-mulaw and mus-alaw (kludges for a kind of 8-bit float),
mus-byte and mus-ubyte (8-bit ints, unsigned in the latter case). A few
DACs want a particular kind of data, but Snd handles that conversion internally.
Anything less than 12 bits will sound bad — Perry Cook's book "Real Sound Synthesis"
has examples.
If you encounter a file
with an unknown format, or a header that has the wrong format,
you can set this field to force Snd to interpret the data in any
way you like. Similar remarks apply to the srate, data-location,
header-type, and channels fields. There are ambiguities in some header
specifications, usually involving big/little endian or signed/unsigned data confusion.
If you encounter a sound that is clipping crazily or is just a burst of noise, try changing these settings.
Some NeXT/Sun (au) header files using byte-wide data
assume the byte is unsigned, whereas most others assume it is signed. Sndlib
treats it as signed by default, so to make one of the unsigned-byte files playable,
(set! (data-format) mus-ubyte)
mus_float_t data is another source of confusion;
there is apparently no agreement on whether the data is between -1.0 and 1.0, or -32768.0 and 32767.0 or anything else.
In this case, Snd assumes -1.0 to 1.0 (except in one special case involving IRCAM headers), and you may have to
set y-bounds to see the actual data.
Yet another gotcha: files with 32-bit integers. Some programs (Glame, apparently, and perhaps Ardour) assume the fraction is
31 bits wide, others (Snd) use whatever its sample width is configured to be; there is no correct or standard
placement of the fixed point, but not to worry! Your data is ok:
(set! (y-bounds) (list -256.0 256.0)) . There are several ways you can handle
these files automatically in Snd. Perhaps the simplest is to use one of the open hooks:
(add-hook! after-open-hook
(lambda (snd)
;; this could also (alternatively) set the y-bounds as above
(if (= (data-format snd) mus-lint)
(set! (data-format snd) mus-lintn))))
|
or (an alternative that sets the underlying database entry, rather than the current in-Snd choice):
If you set any of these fields, the sound's index may change (there can be an embedded update-sound).
To deal with MPEG, OGG, Flac, or Speex files, see examp.scm (mpg) or misc.scm (mpg123 and ogg123).
Octave/WaveLab ASCII files can be translated by read-ascii (examp.scm).
To turn a data-format number into a string, use mus-data-format-name. To get
the data format of some sound file, use mus-sound-data-format.
The default output (new-sound, and
save-sound-as) data-format is default-output-data-format.
To change a sound file's data-format, use save-sound-as.
|
|
data-location snd
|
|
This gives the location (in bytes) of the sound samples in the file represented by 'snd'. In a raw (headerless) file,
this is 0, but normally the data comes after some portion of the header.
To get the data-location of some sound file, use mus-sound-data-location.
If you set this field (you don't want to do this — it is a law of nature that you will forget the original setting!), the underlying file is immediately rewritten.
|
|
data-size snd
|
|
This gives the size (in bytes) of the sound data in the file represented by 'snd'.
If you set this field, the underlying file is immediately rewritten (the header is changed; I don't
think the file is truncated, but no matter what happens, it is not my fault).
Next/Sun files treat the size field as purely "advisory", so an incorrect data size is often
ignored in that case.
|
|
delete-sample samp snd chn edpos
|
|
This deletes sample 'samp' in the given channel.
|
|
delete-samples samp samps snd chn edpos
|
|
This deletes a block of samples. The deleted portion starts at sample 'samp' and runs for 'samps' samples.
See delete-to-zero or delete-selection-and-smooth in extensions.scm.
|
|
dot-size snd chn
|
|
This gives the size in pixels of dots when graphing with dots (default: 1); this affects graph-styles such as graph-lollipops . See graph-hook or auto-dot in examp.scm.
|
|
env-channel clm-env-gen beg dur snd chn edpos
|
|
env-channel is the regularized version of env-sound. 'clm-env-gen'
can be either a CLM envelope generator or an envelope (a list of breakpoints). (env-channel '(0 0 1 1 2 0)) .
To get .1 seconds of attack and decay:
(let ((dur (/ (frames) (srate))))
(env-channel (list 0 0 .1 1 (- dur .1) 1 dur 0)))
An envelope in Snd is a list of breakpoints. It can be packaged as a CLM generator (an 'env') via make-env.
It can be declared via define just like any other variable, or with defvar (for CLM/Snd intercompatibility),
or with define-envelope.
|
|
env-channel-with-base envelope-or-env-gen base beg dur snd chn edpos
|
|
env-channel-with-base is a slight variation on env-channel. There are times when it's a bother
to call make-env just to get an exponential envelope.
|
|
env-sound envelope samp samps env-base snd chn edpos
|
|
env-sound applies the amplitude 'envelope' to the given channel starting
at sample 'samp' for 'samps' samples with connecting segments
based on 'env-base'. 'env-base' defaults to 1.0.
'samp' defaults to 0. 'samps' defaults to the full duration.
'envelope' is a list containing the breakpoint values
(as in CLM) or an env generator.
|
(env-sound '(0 0 1 1 2 0))
env_sound([0.0, 0.0, 1.0, 1.0, 2.0, 0.0])
'( 0.0 0.0 1.0 1.0 2.0 0.0 ) env-sound
|
|
As mentioned in sndclm.html,
'env-base' determines how the break-points are connected. If it is 1.0 (the
default), you get straight line segments. 'env-base' = 0.0 gives a step
function (the envelope changes its value suddenly to the new one without any
interpolation). Any other positive value becomes the exponent of the exponential curve
connecting the points. 'env-base' < 1.0 gives convex curves (i.e. bowed
out), and 'env-base' > 1.0 gives concave curves (i.e. sagging).
If you'd rather think in terms of e^-kt, set 'env-base' to (exp k).
See env.lisp for a CLM instrument that shows the relation between the connecting
curve's exponent and 'env-base'. Here's a brief restatement:
(define (compare-exp k)
(let ((e (make-env (list 0 1 1 (exp (- k))) :base (exp k) :length 11)))
(do ((i 0 (+ 1 i )))
((= i 10))
(snd-print (format #f "~A ~A~%" (env e) (exp (* (- k) (/ i 10.0))))))))
|
If 'envelope' is a CLM env generator, 'env-base'
is ignored.
|
|
fft-log-frequency snd chn
|
|
This returns whether the spectrum frequency axis is logarithmic (#t) or linear (#f, the default). If logarithmic, the lower end
is set by log-freq-start which defaults to 32Hz.
|
|
fft-log-magnitude snd chn
|
|
This returns whether the spectrum magnitude axis is in decibels (#t) or linear (#f, the default). If in decibels, the
minimum displayed is set by min-dB which defaults to -60.
|
|
fft-window snd chn
|
|
This sets the choice of fft data window (default: blackman2-window )
bartlett-hann-window bartlett-window blackman2-window blackman3-window
blackman4-window bohman-window cauchy-window connes-window
dolph-chebyshev-window exponential-window flat-top-window gaussian-window
hamming-window hann-poisson-window hann-window kaiser-window
parzen-window poisson-window rectangular-window riemann-window
samaraki-window tukey-window ultraspherical-window welch-window
blackman5-window blackman6-window blackman7-window blackman8-window
blackman9-window blackman10-window rv2-window rv3-window
rv4-window mlt-sine-window papoulis-window dpss-window
sinc-window
The Hann window is sometimes called Hanning in the DSP literature, apparently
as an in-joke. For an extensive discussion of these windows, see
Fredric J. Harris, "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform", Proceedings of the
IEEE, Vol. 66, No. 1, January 1978, with updates from: Albert H. Nuttall, "Some Windows with Very Good Sidelobe Behaviour", IEEE Transactions
of Acoustics, Speech, and Signal Processing, Vol. ASSP-29, 1, February 1981, and of course, Julius Smith's DSP web site.
|
|
fft-window-alpha snd chn
|
|
The ultraspherical window has two "family" parameters; the one named "mu" is called "beta" here,
to parallel its use in related windows; the other one, named "xmu" is named "alpha" here,
for no good reason. fft-window-alpha sets the shape of the side lobes; see
"Design of Ultraspherical Window Functions with Prescribed Spectral Characteristics", Bergen and Antoniou, EURASIP JASP 2004
(also available on-line) for details.
|
|
fft-window-beta snd chn
|
|
Some fft windows have a parameter, often named alpha or beta, that chooses one from a family of possible windows.
The actual (underlying) beta values are dependent on the window choice, but
in Snd, fft-window-beta is scaled to fit the current window's range of values, so
its value here should fall between 0.0 and 1.0.
|
|
fft-with-phases snd chn
|
|
This returns whether the single FFT display includes phase information (the default is #f).
|
|
file-name snd
|
|
This returns the sound's complete (or "absolute") file name; the directory is included; see short-file-name
if you don't want all the directory junk. See examp.scm for many examples.
|
|
filter-channel env order beg dur snd chn edpos trunc origin
|
|
The regularized version of filter-sound. If the end of the filtered portion is not the end of the sound,
the 'trunc' argument determines whether the filtered sound is truncated at that point (the default: #t),
or mixed with the overlapping section, similar to the truncate argument to filter-selection.
'env' can be either the frequency response envelope, or a vct containing the desired coefficients.
|
|
filter-sound env order snd chn edpos origin
|
|
filter-sound applies an FIR filter of order 'order' (actually one more than the nominal order)
and frequency response 'env'
to the given channel. 'env' can also be a vct containing the filter coefficients,
or any CLM filtering generator
(e.g. comb, formant, one-pole, iir-filter, etc). The generator
is called in C, not Scheme, so this is the fastest way to apply
CLM filtering to a sound. See also clm-channel.
(filter-sound '(0 1 1 0) 1024) ; FIR filter given frequency response
(filter-sound (vct .1 .2 .3 .3 .2 .1) 6) ; FIR filter given actual coefficients
(filter-sound (make-fir-filter 6 (vct .1 .2 .3 .3 .2 .1))) ; CLM FIR filter
(filter-sound (make-delay 120)) ; CLM delay (same as insert-silence)
(filter-sound (make-formant 1200 .99)) ; CLM formant
(filter-sound (make-filter 2 (vct 1 -1) (vct 0 -0.99))) ; remove DC
|
If you want to use the cascade filter structure, rather than the canonical
form of CLM's filter generator:
(define (make-biquad a0 a1 a2 b1 b2)
(make-filter 3 (vct a0 a1 a2) (vct 0.0 b1 b2)))
If you have coefficients for the cascade form, but have no scruples about using
some other form, see cascade->canonical in dsp.scm, and the examples that follow.
Filters in Snd:
filter a sound: filter-sound, filter-channel, and clm-channel
filter the selection: filter-selection, filter-selection-and-smooth
CLM filter generators: filter, one-pole, formant, comb, notch, all-pass, etc
lowpass filter: make-lowpass in dsp.scm
highpass filter: make-highpass in dsp.scm
bandpass filter: make-bandpass in dsp.scm
bandstop filter: make-bandstop in dsp.scm
the usual analog filters (Butterworth, Chebyshev, Bessel, Elliptic): analog-filter.scm
Butterworth filters: make-butter-high-pass, make-butter-low etc in dsp.scm, used in new-effects.scm
IIR filters of various orders/kinds: dsp.scm
Hilbert transform: make-hilbert-transform in dsp.scm
differentiator: make-differentiator in dsp.scm
block DC: see example above, dc-block in prc95.scm, clean-channel in clean.scm, or stereo-flute in clm-ins.scm
hum elimination: see eliminate-hum and notch-channel in dsp.scm
hiss elimination: notch-out-rumble-and-hiss
smoothing filters: moving-average, weighted-moving-average, exponentially-weighted-moving-average
notch-filters: notch-channel and notch-selection
arbitrary spectrum via FIR filter: spectrum->coeffs in dsp.scm
invert an FIR filter: invert-filter in dsp.scm
filtered echo sound effect: flecho in examp.scm
time varying filter: fltit in examp.scm
draw frequency response: use envelope editor or filter control in control panel
Moog filter: moog.scm
Savitzky-Golay filter: savitzky-golay-filter
Click reduction: remove-clicks, clean-channel
FIR filter as virtual edit: virtual-filter-channel
LADSPA-based filter effects: see ladspa.scm
Max Mathews resonator: firmant, maxf.scm, maxf.rb
Spectral edit dialog: Envelope Editor
graphical equalizer filter bank: graphEq
nonlinear (Volterra) filter: volterra-filter
Kalman filter: kalman-filter-channel
see also convolution, physical modeling, reverb, and fft-based filtering
Scheme srfi-1 filter function: %filter.
|
|
|
find-channel proc sample snd chn edpos
|
|
This function finds the sample that satisfies the function 'proc'. 'sample'
determines where to start the search.
If 'proc' returns some non-#f value, find-channel returns a list with that value (if optimization is off) and the sample number.
In the find dialog and in C-s or C-r searches, if the value returned is an integer, the cursor is offset by that number of samples.
>(find-channel (lambda (y) (> y .1)))
(#t 4423)
>(find-channel (lambda (y) (and (> y .1) 'a-big-sample)))
(a-big-sample 4423) ; if optimization is on, this will be (#t 4423)
>lambda: <{ y }> 0.1 y f< ; find-channel
'( #t 4423 )
I didn't bring out the search direction (find-channel is built on scan-channel which assumes it is
scanning forwards), but it's not hard to write a function to find in reverse:
(define* (find-channel-in-reverse proc beg snd chn edpos)
(let* ((sample (or beg (- (frames snd chn) 1))) ; or cursor?
(reader (make-sampler sample snd chn -1 edpos))
(result #f))
(do ((i sample (- i 1)))
((or result (< i 0))
(and result
(list result (+ 1 i))))
(set! result (proc (reader))))))
|
|
find-sound filename nth
|
|
find-sound returns the sound object of 'filename' or
#f if no sound is found that matches 'filename'. If there is (or might be) more than one file
open with the given filename, the 'nth' parameter (which defaults to 0) chooses which to return.
Leaving aside the 'nth' parameter, find-sound could be defined as:
(define (find-sound name)
(call-with-current-continuation
(lambda (return)
(for-each
(lambda (snd)
(if (or (string=? (short-file-name snd) name)
(string=? (file-name snd) name))
(return snd)))
(sounds))
#f)))
|
See popup.scm, and files-popup-buffer, open-next-file-in-directory, and the "Buffer Menu" code in examp.scm.
|
|
finish-progress-report snd chn
|
|
This ends an on-going progress report (a visual indication of how far along some time-consuming process is).
See progress-report.
|
|
frames snd chn edpos
|
|
This returns current length in samples of the channel 'chn'. Used with set!, this either truncates
the sound or pads it with zeros at the end.
|
|
free-player player
|
|
free-player frees all resources associated with 'player' and remove it from the play-list.
|
|
graph data xlabel x0 x1 y0 y1 snd chn force-display show-axes-choice
|
|
This function displays a graph of 'data' in a separate display per channel. The x axis
is labelled 'xlabel', the x axis units go from 'x0' to 'x1' (the default is 0.0 to 1.0),
the y axis goes from 'y0' to 'y1' (the default fits the data), and the display is
associated with channel 'chn' in 'snd'.
(graph (vct 0 .1 .2 .3 .4 .3 .2 .1 0) "roof")
The current slider values can be read from x-position-slider,
x-zoom-slider, etc. The 'data' argument can be a list of vcts; each is graphed at the same time, following the sequence of
colors used when channels are superimposed. If 'data'
is a list of numbers, it is assumed to be an envelope (a list of breakpoints).
If 'force-display' is #f (the default is #t), the graph is not
explicitly drawn; this is useful when you're calling graph from
the lisp-graph-hook, where the redisplay is automatic.
'show-axes-choice' sets the show-axes choice for the lisp graph.
|
|
graph-style snd chn
|
|
graph-style determines how sound data is displayed (default: graph-lines ).
The choices are:
graph-lines graph-dots graph-filled graph-lollipops graph-dots-and-lines
In the set! case, if no 'snd' is specified, all graph-styles are set to the
new value. If 'snd' is given, the three graph styles for that sound's channels (or channel 'chn') are
set. See time-graph-style, lisp-graph-style, and
transform-graph-style to override the default for a specific graph.
|
|
graphs-horizontal snd chn
|
|
This determines whether channel graphs (the time domain, spectrum, and lisp graphs)
are arranged
vertically
or horizontally (the latter is the default).
|
|
grid-density snd chn
|
|
This controls the spacing of axis ticks; the default is 1.0. If grid-density is less than 1.0, more ticks are squeezed
in; if greater than 1.0, fewer ticks are displayed. This mainly affects the grid display
(show-grid).
|
|
header-type snd
|
|
This returns the header type (e.g. mus-aiff ) of the file that underlies 'snd'.
Snd can read about 60 header types, and write 7 or so.
"aiff" and "aifc" come from Apple, "riff" is the Microsoft "wave" header,
"rf64" is the European Broadcast Union's 64-bit RIFF replacement,
"nist" comes from the NIST-Sphere package, "next" or "sun" is the Next/Sun
(".au") header, "ircam" is IRCAM's extension of the Next header,
"caf" is Apple's 64-bit AIFC replacement,
and "raw"
means the sound file has no header. If you change the header type to "raw",
any existing header is removed.
Each header type has its own peculiarities; if in doubt, use mus-next because it is simple,
and can handle any data format that Snd can write (whereas each of the others is restricted in this regard).
The writable header types are mus-next , mus-nist ,
mus-aiff (obsolete, rarely needed), mus-aifc , mus-riff , mus-rf64 , mus-caff ,
mus-ircam , and mus-raw (no header). For technical descriptions of the headers,
see headers.c; for actual sound files, see sf.tar.gz at ccrma-ftp.
To turn a header type number into a string, use mus-header-type-name. To get
the header type of some sound file, use mus-sound-header-type.
If you set the header-type, the sound file is rewritten with the new header. The default output
(new-sound, and
save-sound-as) header type is default-output-header-type.
To read or write your own headers (or some header that isn't built-in),
I recommend using either open-hook or open-raw-sound-hook:
in the latter case, when you open the file with the unsupported header,
Snd will throw up its hands and say "maybe it's a raw (headerless)
sound"; it will then look at open-raw-sound-hook before trying
other fallbacks (such as the Raw File Dialog).
See examp.scm or misc.scm (MPEG, OGG, etc).
|
|
insert-sample samp value snd chn edpos
|
|
This inserts sample 'value' at sample 'samp' in the given channel
|
|
insert-samples samp samps data snd chn edpos auto-delete origin
|
|
This inserts 'samps' samples of 'data' (normally a vct) starting at sample 'samp' in the given channel.
'data' can be a filename.
The regularized version of this is:
(define* (insert-channel data beg dur snd chn edpos)
(insert-samples beg dur data snd chn edpos))
To insert a block of samples of a given value: (insert-samples beg dur (make-vct dur val))
If 'data' is a file, it is not deleted by Snd unless 'auto-delete' is #t.
|
|
insert-silence beg num snd chn
|
|
This inserts 'num' zeros at 'beg' in the given channel. pad-channel is the regularized version,
with one small change: insert-silence forces 'beg' to be within the current sound, but pad-channel pads out to 'beg' if
'beg' is past the end of the sound. (And, as usual in these cases, insert-silence follows the sync
field, whereas pad-channel ignores it).
|
|
insert-sound file beg in-chan snd chn edpos auto-delete
|
|
This inserts channel 'in-chan' of 'file' at sample 'beg' in the given channel.
'beg' defaults to the cursor position; if 'in-chan' is not given, all
channels are inserted. To append one sound to another, padding at the end with some silence:
(define* (append-sound file (silence 1.0))
(insert-sound file (frames))
(insert-silence (frames) (round (* (srate) silence))))
'file' is not deleted by Snd unless 'auto-delete' is #t.
|
|
integer->sound i
|
|
In olden times, a sound was handled in Snd code as an integer; nowadays, it's an object (although the integer approach still works).
This function, and its companion sound->integer, exist mainly to convert
old code to the current style.
|
|
left-sample snd chn
|
|
This returns the position in samples of the left edge of the time domain
waveform for the given channel.
To get the data currently displayed in the time domain window:
See also move-one-pixel.
|
|
lisp-graph? snd chn
|
|
lisp-graph? returns #t if the lisp-generated graph is currently displayed ("lisp" here means any extension language).
The lisp graph section is also active if there's a drawing function
on the lisp-graph-hook.
|
|
lisp-graph-style snd chn
|
|
This determines how lisp-generated data is displayed.
The choices are:
graph-lines graph-dots graph-filled graph-lollipops graph-dots-and-lines
|
|
make-player snd chn
|
|
This function makes a new player associated with the given channel.
A player is a sort of wrapper for a channel of a sound that supports
all the control-panel functions. Once created, you can set these
fields, then call add-player to add this channel to the list of
channels either being played (if a play is in progress) or about
to be played. Once some player is in the play-list, you can start
the play with start-playing, and stop it prematurely with either
stop-player or stop-playing. These functions make it possible
to build custom control panels. Here's an example that plays a
sound with individual amplitudes for the channels:
(define play-with-amps
(lambda (sound . amps)
(let ((chans (channels sound)))
(do ((chan 0 (+ 1 chan)))
((= chan chans))
(let ((player (make-player sound chan)))
(set! (amp-control player) (list-ref amps chan))
(add-player player)))
(start-playing chans (srate sound)))))
(play-with-amps 0 1.0 0.5) ;plays channel 2 of stereo sound at half amplitude
|
|
|
See play-with-envs in enved.scm,
play-syncd-marks in marks.scm, start-dac in play.scm,
and add-amp-controls in snd-motif.scm.
|
|
make-variable-graph container name length srate
|
|
make-variable-graph is a part of the variable-display mechanism in snd-motif.scm. It creates the
sound/channel pair that displays a graph or spectrum of the arbitrary data accessed via channel-data.
See oscope.scm.
|
|
map-chan func start end edname snd chn edpos
|
|
map-chan applies 'func' to samples in the specified channel.
It is the old ("irregular") version of map-channel.
|
|
map-channel func beg dur snd chn edpos origin
|
|
map-channel is one of the standard ways to change a sound. It applies 'func' to each sample
replacing the current value with whatever 'func' returns.
As usual, 'beg' defaults to 0, 'dur' defaults to the full length of the sound,
'snd' and 'chn' default to the currently selected sound, and 'edpos' to the
current edit history list position. 'origin' is the edit history name of the current
operation.
'func', a procedure of one argument (the current sample),
can return #f, which means that the data passed in is
deleted (replaced by nothing), or a number which replaces the
current sample,
or #t which halts the mapping operation, leaving trailing samples
unaffected, or a vct
the contents of which are spliced into the edited version, effectively
replacing the current sample with any number of samples. This sounds
more complicated than it is! Basically, a map-channel function receives
each sample and returns either #f (no corresponding output), a number
(the new output), or a bunch of numbers.
If every value returned for a given channel is #f, the data is not edited.
Here we add 0.2 to every sample in a channel:
Scheme:
>(map-channel (lambda (y) (+ y 0.2)))
0
Ruby:
>map_channel(lambda do |y| y + 0.2 end)
-0.0015869140625
Forth:
>lambda: <{ y }> y 0.2 f+ ; map-channel
-0.00158691
In the next sequence, we replace a sound by the difference between successive
samples (a high-pass effect), then undo that by adding them back together,
then check to see how close our reconstruction is to the original:
> (let ((y0 0.0)) (map-channel (lambda (y) (let ((diff (- y y0))) (set! y0 y) diff))))
0
> (let ((y0 0.0)) (map-channel (lambda (y) (let ((add (+ y y0))) (set! y0 add) add))))
0
> (let ((rd (make-sampler 0 0 0 1 0))) (map-channel (lambda (y) (- y (rd)))))
0 ; the sampler is reading the unedited form of the sound
> (maxamp) ; i.e. how big is the biggest difference
0.0
(define* (cosine-channel (beg 0) dur snd chn edpos)
(map-channel
(let* ((samps (or dur (frames snd chn)))
(incr (/ pi samps))
(angle (* -0.5 pi)))
(lambda (y)
(let ((val (* y (cos angle))))
(set! angle (+ angle incr))
val)))
beg dur snd chn edpos))
|
Here's a slightly more involved example;
we define a function that finds silent portions and replaces them with
something:
(define (map-silence in-silence replacement)
(let ((buffer (make-moving-average 128))
(silence (/ in-silence 128)))
(lambda (y)
(let ((sum-of-squares (moving-average buffer (* y y))))
(if (> sum-of-squares silence) y replacement)))))
(map-channel (map-silence .01 0.0)) ; squelch background noise
(map-channel (map-silence .001 #f)) ; remove silences altogether
|
Here we're using 'buffer', a CLM moving-average generator, to track the
RMS value of the last 128 input samples.
When that falls below
the argument 'silence', we replace the current sample with 'replacement'.
It may be easier in complex cases to use with-sound rather than map-channel.
See step-src for example.
It is possible to break out of a map, flushing any edits, via call-with-current-continuation:
(define ctr 0)
(call-with-current-continuation
(lambda (return)
(map-channel (lambda (val)
(set! ctr (+ 1 ctr))
(if (> ctr 100)
(return "quitting!")
val)))))
|
It is also possible to stop, then continue map-channel:
(define go-on #f)
(map-channel (lambda (y)
(call-with-current-continuation
(lambda (stop)
(if (> y 1.0)
(begin
(set! go-on stop)
(throw 'oops)))))
.2))
|
If this hits a sample > 1.0, it will print 'oops and put the continuation in the variable 'go-on'.
(go-on) will continue where you left off. (I'm not sure how far this can be pushed, or
whether it's a good idea — you may end up with unclosed files and so on).
If the editing action is not mapping something over the current sound, it is
safest to write a temp file with the new data, then pass that to set-samples
with the 'trunc' argument set to #t. This way you don't assume the new sound
will fit in memory (as in using vct->channel for example).
Use snd-tempnam to get a temporary filename that reflects the current
temp-dir setting. The env-sound-interp function in examp.scm
is an example of this.
(define* (map-sound-chans proc (beg 0) dur snd edpos origin)
(do ((i 0 (+ 1 i)))
((= i (channels snd)))
(map-channel proc beg dur snd i edpos origin)))
|
An esoteric aside: map-channel sets up the sampler before calling the procedure, so if that procedure edits
the sound itself (independent of map-channel), the result will be all such edits after the current edit, then the map-channel result
applied to the original (not the newly edited) data. That is,
(let ((first #t))
(map-channel (lambda (y)
(if first (set! (sample 0) 1.0))
(set! first #f)
(* y 2))))
will return with two edits registered in the edit history list; the map-channel result will be the original data doubled;
the preceding edit in the list will be the (set! (sample 0) 1.0) which the map-channel ignores.
|
|
maxamp snd chn edpos
|
|
This returns the max amp of the given channel, or the overall maxamp of snd if no channel argument is given.
Used with set!, it is equivalent to scale-to.
(define (maxamp-all)
"(maxamp-all) returns the current maxamp of all currently open sounds"
(apply max (map (lambda (snd) (apply max (maxamp snd #t))) (sounds))))
|
|
|
maxamp-position snd chn edpos
|
|
This gives the location (sample number) of the maximum sample in the given channel.
|
|
max-transform-peaks snd chn
|
|
This returns the maximum number of transform peaks reported.
The default is 100. max-transform-peaks affects both the fft display (if show-transform-peaks)
and the peaks function.
|
|
min-dB snd chn
|
|
This sets the minimum dB value displayed in various graphs (the default is -60.0).
Due to problems with arithmetic underflows in sqrt, the spectrum functions set the lowest
actual dB value calculated to -140.0 or -180.0 (depending on which function is called and so on).
|
|
new-sound :file :header-type :data-format :srate :channels :comment :size
|
|
new-sound creates a new sound named 'file'. The following function opens a new sound named "test.snd",
extends it to 'dur' samples, and initializes all samples to 'val':
(define (init-sound val dur)
(let ((ind (new-sound "test.snd" :size dur)))
(map-channel (lambda (y) val))
ind))
If the 'header-type' and other
arguments are not specified, they
default to the current default-output-header-type and
related settings. Data formats are (b=big-endian, l=little, u=unsigned):
mus-bshort mus-lshort mus-mulaw mus-alaw mus-byte mus-ubyte mus-bfloat
mus-lfloat mus-bint mus-lint mus-b24int mus-l24int mus-bdouble mus-ldouble
mus-ubshort mus-ulshort
Header-types are:
mus-next mus-aifc mus-riff mus-rf64 mus-nist mus-raw mus-ircam mus-aiff
mus-soundfont mus-bicsf mus-voc mus-svx mus-caff
To be informed whenever a new sound is created, use new-sound-hook (see ws.scm).
|
|
normalize-channel norm beg dur snd chn edpos
|
|
normalize-channel
scales (changes the amplitude) of a sound so that its new peak amplitude is 'norm'. This
is the "regularized" form of scale-to.
The multichannel version is normalize-sound in extensions.scm.
|
|
open-raw-sound :file :channels :srate :data-format
|
|
This opens 'file' as a raw (no header) sound in the layout specified.
If the file has a header, it is not ignored (use (set! (data-format ...))
and friends to get around this). If the header is messed up, you can override its settings by
giving the correct values in the call to open-raw-sound.
(define mpg
(lambda (mpgfile rawfile chans)
"(mpg file tmpname chans) converts file from MPEG-3 to raw 16-bit samples using mpg123"
(system (format #f "mpg123 -s ~A > ~A" mpgfile rawfile))
(open-raw-sound rawfile 1 44100 (if (little-endian?) mus-lshort mus-bshort))))
|
There's a more elaborate version of this function in examp.scm. See also open-raw-sound-hook.
|
|
open-sound filename
|
|
open-sound opens 'filename' and returns the sound object; this is equivalent to the File:Open option. view-sound
opens a sound read-only, or you can set read-only by hand. close-sound
closes a file opened by open-sound. There are a variety of hooks that are invoked during the sound opening process:
during-open-hook, open-hook,
after-open-hook,
initial-graph-hook, open-raw-sound-hook.
The sequence of actions is:
bad header?: bad-header-hook — can cancel request
no header?: open-raw-sound-hook — can cancel request
file ok:
open-hook — can change filename
file opened (no data read yet)
during-open-hook (can set prescaling etc)
data read, no graphics yet
after-open-hook
initial-graph-hook
There are
other ways to get at sound file data: make-sampler can be given a filename,
rather than a sound; file->vct in examp.scm;
mus-sound-open-input and
there are a variety of CLM-based functions such as
file->sample and
file->array.
|
|
pad-channel beg dur snd chn edpos
|
|
pad-channel inserts 'dur' zeros at 'beg' in the given channel. This is the regularized
version of insert-silence. To set a block of samples to zero, use
scale-channel with a scaler of 0.0.
To insert a block of arbitrary-valued samples:
(define* (block-channel value (beg 0) dur snd chn edpos)
(pad-channel beg dur snd chn edpos) ; insert 'dur' samples, ptree-channel sets their values
(ptree-channel (lambda (y) value) beg dur snd chn))
|
We could also use map-channel here (rather than ptree-channel), but this version
uses only virtual edits, so no matter how big the block of samples we insert,
no disk space or memory is needed.
The multichannel version is pad-sound in frame.scm.
|
|
pausing
|
|
pausing is #t if sound output is currently paused. You can unpause the sound by setting pausing to #f,
and pause it by setting pausing to #t. If you pause a sound (via C-click of the play button, for example),
then call play (via a key binding perhaps), the sound remains paused by default. To
cancel the current pause and restart with the new play command:
|
|
peaks file snd chn
|
|
peaks displays fft peak information. If 'file' is not null, it writes
the information to that file, otherwise it posts the data in a help window
(where it can be selected and pasted elsewhere). The maximum number of peaks reported is set
by max-transform-peaks.
(add-hook! after-transform-hook (lambda (a b c)
(peaks))) ; post a detailed list of peaks after each FFT
|
|
play object :start :end :channel :edit-position :out-channel :with-sync :wait :stop :srate :channels
|
|
play plays 'object'. If no arguments are passed, it plays the currently selected sound.
'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.
'start' is where to start playing (a sample number, defaults to 0).
'end' is where to stop playing.
'channel' is which channel to play (the default is to play all channels).
'edit-position' is which edit history list entry to play, where that is relevant. The default is the current entry.
'out-channel' is which DAC channel to send the samples to.
'with-sync' sets whether to include all objects sync'd to the current one (default is no, #f).
'wait' sets whether the function call should wait until the play is complete before returning (default is no, #f).
'stop' is a procedure called when the play completes.
'srate' and 'channels' are for one special case, described below.
(play) ; play current sound, all chans from start to end
(play 0 :channel 1) ; play just the second channel of sound 0
(play ((selected-sound) cursor)) ; play starting from the cursor
(play (integer->sound 1) (round (* 3.0 (srate))) :channel 3) ; play snd 1, chan 3 (4th chan), start at 3.0 secs
(play (selected-sound) 0 :with-sync #t) ; play sync'd sounds
(play (selected-sound) 0 :end (round (* 3.0 (srate)))) ; play from start for 3.0 secs
(play (selected-sound) 0 :edit-position 2) ; play the version at edit history position 2
(play (integer->sound 0) 0 :channel 2 :out-channel 0) ; play chan 2, but send it to DAC chan 0
(play (selected-sound) (mark-sample (integer->mark 0)) :end (mark-sample (integer->mark 1))); play between marks 0 and 1
(play (selection)) ; play the selection
(play #f :srate 44100 :channels 2) ; open DAC and run, stop with stop-playing
(play "1a.snd") ; play 1a.snd
(play "1a.snd" 1000 4000) ; play 1a.snd from sample 1000 to 4000
If 'stop' is a procedure of one argument, it is called when the play process stops.
The argument passed to the stop procedure provides the reason the play is stopping; it will be 0 if the play completed normally.
This is intended mainly for looping plays, as in play-often.
(play (selected-sound) 0 :stop (lambda (reason) ; if interrupted, say so in the listener
(if (not (= reason 0))
(snd-print ";play interrupted"))))
The 'edit-position' argument makes it easier to try "A:B" comparisons; this plays the version before the latest edit:
(play (selected-sound) :edit-position (- (edit-position) 1))
The following code binds the "p" key to play all channels of the current sound from the cursor, and
the "P" key to play the previous version of the current sound:
And here we play from the cursor with a moving ("tracking") cursor:
If 'object' is #f, the :srate and :channels arguments set up the DAC.
The DAC then stays open until you call stop-playing. This is useful
when you're using bind-key and play to trigger sounds, but want the output to have more channels
than the various inputs.
(bind-key #\o 0
(lambda () ; send oboe.snd to chan 0
(play "oboe.snd" :out-channel 0)))
(bind-key #\p 0
(lambda () ; send pistol.snd to chan 1
(play "pistol.snd" :out-channel 1)))
;;; Now open a sound (so you have a non-listener pane to type to)
(play #f :srate 22050 :channels 2) ; srate 22050, 2 output chans
;;; this holds the DAC open indefinitely
;;; Now type o and p in the sound pane until you want to quit, then
(stop-playing)
|
Finally, if 'object' is a function, it is called on every sample; if it returns a number, that number is
sent to the DAC; if it returns #f, it stops. play-mixes uses this
function option to time the playing of each mix in a sequence of mixes. Another example is play-sine:
(define (play-sine freq amp)
"(play-sine freq amp) plays a 1 second sinewave at freq and amp"
(let* ((len 22050)
(osc (make-oscil freq)))
(play (lambda ()
(set! len (- len 1))
(if (<= len 0) ; we've sent 22050 samples, so it's time to stop
#f
(* amp (oscil osc)))))))
|
Here's another example that plays a sound file, skipping any portion that looks like silence:
(define (play-skipping-silence file)
(let ((buffer (make-moving-average 128))
(silence (/ .001 128))
(rd (make-sampler 0 file))
(sum-of-squares 0.0)
(y 0.0))
(play (lambda ()
(let loop ()
(set! y (rd))
(set! sum-of-squares (moving-average buffer (* y y)))
(if (sampler-at-end? rd)
#f
(if (> sum-of-squares silence)
y
(loop))))))))
|
play one channel: (play sound-object :channel n), play button in control panel or files dialog
play from cursor: C-q and example above
play from cursor with tracking cursor: pfc above
play the selection: (play (selection)), C-x p
play a region: (play region-object), C-x p, play button in Region dialog
play a mix: (play mix-object), play button in Mix dialog
play a sequence of mixes: play-mixes
play from mark: click or drag triangle (control-click for all chans)
play continuously between two marks: loop-it
stop playing: C-g, C-t, stop-playing, set playing to #f
pause or resume playback: space, set pausing
play repeatedly: play-often
play repeatedly until C-g: play-until-c-g
play region repeatedly: play-region-forever
play a file upon a keystroke: bind-key
play using an external program: (system "sndplay wood16.wav")
play a sine-wave or spectrum: play-sine and play-sines
play arbitrary mixtures of things: make-player and related functions, play-syncd-marks
send arbitrary data to the DAC: mus-audio-write, start-dac
play after sending the data through some function: play-sound
play with applied amplitude envelope: play-with-envs, play-panned
play an external file: (play "file")
|
The "reasons" that might be passed to the stop-procedure are:
0 play completed normally
1 file is being closed
2 play button unset
3 stop-playing function called
4 C-g
5 DAC error (no available channel)
6 play error (audio setup problem)
7 apply requested (control panel)
8 file edited
9 C-t
The hooks called during a play operation are:
when a play request occurs: start-playing-hook — can cancel the request,
also start-playing-selection-hook
(any number of sounds can be playing at once)
as each buffer is sent to the audio device: play-hook and dac-hook
as each sound ends: stop-playing-hook, stop-playing-selection-hook
close audio device: stop-dac-hook
|
|
player-home player
|
|
This returns a list of the sound and channel number associated with player.
|
|
playing
|
|
This returns #t if sound output is currently in progress. You can also start playing by setting playing to #t (equivalent
to calling start-playing with default arguments), and stop by setting it to #f
(equivalent to stop-playing).
playing only notices Snd-instigated "play" processes;
mus-audio-open-output is invisible to it.
|
|
players
|
|
This returns a list of currently active players.
|
|
player? obj
|
|
This returns #t if 'obj' is an active player.
|
|
position->x xpos snd chn axis
|
|
This returns the x axis value that corresponds to the graph (screen pixel) position 'xpos'.
To find the sample that the mouse is pointing at, given the current mouse position,
(round (* (srate snd) (position->x x snd chn)))
See gui.scm for examples.
|
|
position->y ypos snd chn axis
|
|
This returns the y axis value that corresponds to the graph (screen pixel) position 'ypos'.
See gui.scm for examples.
|
|
progress-report pct snd chn
|
|
The functions start-progress-report, progress-report, and
finish-progress-report handle the animated hour-glass icon
that hopes to amuse the idle user while some long computation is in progress.
The 'pct' argument is a float between 0.0 and 1.0 which indicates how
far along we are in the computation; there are only 20 separate
icons, so there's no point in calling this more often than that.
start-progress-report posts the initial icon, and finish-progress-report
removes it. If the icons are not available, a message is posted in
the sound's minibuffer using 'name' to identify itself.
See ladspa.scm.
|
|
prompt-in-minibuffer msg callback snd raw
|
|
This posts 'msg' in the sound's minibuffer and when you respond,
it calls 'callback' with the response as the callback's argument.
If 'callback' is specified it should be either #f or a function of
one argument: the raw response if 'raw' is #t, otherwise the evaluated response.
For example, the following fragment asks for
a yes-or-no response, then takes some action:
(define* (yes-or-no question action-if-yes action-if-no snd)
(prompt-in-minibuffer question
(lambda (response)
(clear-minibuffer)
(if (string=? response "yes")
(action-if-yes snd)
(action-if-no snd)))
snd #t))
|
See eval-over-selection in extensions.scm for a more useful example.
We could also use a continuation here:
(define (prompt msg default)
(call-with-current-continuation
(lambda (rsvp)
(prompt-in-minibuffer msg rsvp)
default)))
|
The 'raw' argument is useful when we want to prompt for yes or no, without forcing the
user to put the answer in double quotes. In the next example, we replace Snd's
built-in C-x k action (which immediately closes the sound) with one that is
more like Emacs (it prompts for confirmation first):
(bind-key (char->integer #\k) 0
(lambda ()
"close sound"
(clear-minibuffer)
(prompt-in-minibuffer
(format #f "close ~S (cr=yes)?" (short-file-name))
(lambda (response)
(if (and (not (c-g?)) ; C-g => no
(or (not (string? response))
(= (string-length response) 0)
(char=? (string-ref response 0) #\y)))
(close-sound)))
#f ; selected sound
#t)) ; treat as string (i.e. don't require double quotes)
#t) ; C-x ...
|
|
|
ptree-channel proc beg dur snd chn edpos env-too init-func origin
|
|
ptree-channel
applies the function 'proc' as a 'virtual edit'; that is, the effect of 'proc'
comes about as an implicit change in the way the data is read.
To be less Orphic:
all the data accesses in Snd go through the edit-history list. The currently
active member of that list
chooses a channel data accessor based on the type of edit. A multiply by 2, for
example, does not multiply anything by 2 internally; it just chooses
the accessor that multiplies by 2 on any read. Since every other data access goes through this reader, from any other
point of view, the data has been multiplied. These accessors make it
unnecessary to save any data in temp files or internal arrays, and editing the edit-history list is very
fast — we just tack a new accessor choice onto the edit-history list, so the edit operation
appears to be instantaneous and memory-less. Lots of other operations
are already being done this way in Snd (deletions, scaling, most
envelopes, some channel swaps, etc). ptree-channel extends the idea to (nearly) arbitrary
functions. When you call
(ptree-channel (lambda (y) (* y 2)))
which has the same effect as
(map-channel (lambda (y) (* y 2)))
the
optimizer makes the parse-tree that represents (lambda (y) (* y 2)) , then the
data accessor system uses that parse-tree every time the data is
read.
If the argument 'env-too' is #t,
the same function is applied to the peak env values to get the new version of the peak env data.
The default is #f, and should be #t only if the old max and min values as processed through 'proc'
will be the new max and min values.
Snd uses the peak env values when the graph of the sound covers very large amounts of data.
If 'env-too' is #f, a background process is launched reading all the sound data through
'proc'; this can be time-consuming, so if you're viewing a half-hour of sound data,
it can take awhile for the ptree-channel results to be displayed.
Here is an example that adds a constant to each sample in a file.
(define* (offset-channel dc (beg 0) dur snd chn edpos)
(ptree-channel (lambda (y) (+ y dc)) beg dur snd chn edpos #t))
The actual edit that takes place is (+ y dc) ; that is, 'dc' is added to
every sample. As subsequent editing takes place, the entries representing the 'offset-channel'
call can become thoroughly fragmented.
If your editing operation has any state (i.e. needs to
know where it is in the data), you need to add an 'init-func' (the 8th argument to ptree-channel).
The 'init-func' takes two arguments, the begin time of the read operation, relative to
the original start of the fragment, and the original fragment duration (both in samples).
It should return
the information the main function ('proc' above) needs to
handle the current fragment correctly. Global variables are not guaranteed to
be set within the body of 'proc', so use a vct for local values that change
as the read operation progresses.
The other new argument to 'proc'
is the read direction; the read operations can change direction at any
time, and any ptree-channel function needs to know how to deal with that.
So, in the complicated, 3 argument case,
the sequence of operations is:
a read is requested, 'init-func' is called with the read start point (relative to the
original segment start),
it returns any state that 'proc' may need
to refer to, then each time a sample is needed from the current sample
reader, 'proc' is called passing it the current underlying sample, the return value of 'init-func',
and the read direction.
Here is an example that mixes a sine wave into the current channel:
(define* (sine-channel freq amp (beg 0) dur snd chn edpos)
(ptree-channel
(lambda (y data forward)
(let* ((angle (data 0))
(incr (data 1))
(val (+ y (* (data 2) (sin angle)))))
(if forward
(set! (data 0) (+ angle incr))
(set! (data 0) (- angle incr)))
val))
beg dur snd chn edpos #f
(lambda (frag-beg frag-dur)
(let ((incr (/ (* 2 pi freq) (srate))))
(vct (fmod (* frag-beg incr) (* 2 pi)) incr amp))))) ; fmod from C
|
In the normal case,
this function just mixes in a sine wave: (+ y (* (data 2) (sin angle)))
where the amplitude scaler is stored in (data 2) . In subsequent
reads, the init-func sets up a vct with the current phase (dependent on the frequency
and the fragment begin sample), the phase increment (dependent on the frequency), and
the amplitude (passed as an argument to sine-channel, but stored in the vct since
the outer function's arguments won't be accessible in the main function ('proc')).
See 'sine-ramp' in extensions.scm for another example.
In our 'sine-channel' function, we passed #f as the 'env-too' argument,
to make sure Snd doesn't blithely apply the sine mix to the peak amplitude
envelopes. When 'env-too' is #t,
'proc' is evaluated over the peak env data, rather than the
original data. This makes redisplay much faster whenever a lot of data
is being displayed, but only works if the function's output at the peak
env min and max values are still the min and max values in the actual
data (this is the case in the sinusoidal envelope, sine-ramp, mentioned above).
When 'proc' is being called to calculate the new peak env,
the duration passed to the init-func is the envelope size.
Here is a
version of cosine-channel given under
map-channel that can handle peak-envs:
(define* (cosine-channel-via-ptree (beg 0) dur snd chn edpos)
;; vct: angle increment
(ptree-channel
(lambda (y data forward)
(let* ((angle (data 0))
(incr (data 1))
(val (* y (cos angle))))
(if forward
(set! (data 0) (+ angle incr))
(set! (data 0) (- angle incr)))
val))
beg dur snd chn edpos #t
(lambda (frag-beg frag-dur)
(let ((incr (/ pi frag-dur)))
(vct (+ (* -0.5 pi) (* frag-beg incr))
incr)))))
|
If the underlying data
has too many previous ptree operations (more than max-virtual-ptrees), map-channel is called instead and the new
data is saved in the normal manner.
If no 'init-func' is specified, the editing procedure ('proc') should not assume anything about
the context in which it is called; in this case, there's no way for 'proc' to know where it starts, or when it is being restarted,
or which direction it is running, so,
the following call:
(let ((ctr 0))
(ptree-channel (lambda (y)
(set! ctr (+ 1 ctr))
(* ctr .0001))))
|
will never reset ctr to 0! Every time a portion of the data is read by Snd, the samples will be
higher. But, the notion of an accessor that returns a different thing each time a sample
is accessed is not foolish:
(define* (dither-channel (amount .00006) beg dur snd chn edpos)
(let ((dither (* .5 amount)))
(ptree-channel
(lambda (y)
(+ y (mus-random dither) (mus-random dither)))
beg dur snd chn edpos #t)))
|
This gives a slightly different take on the sound each time you look at it or listen to it;
the dithering is never exactly the same. But if the details of the noise don't
matter (presumably the case with dithering), the difference is unproblematic. You're editing a sort
of mobile in sound (analogous to mobile sculpture).
One major limitation of ptree-channel with an 'init-func' is that save-state
currently doesn't know how to save the enclosing environment along with the init-func. So,
(let ((outer 0.5))
(ptree-channel (lambda (y data forward)
(declare (y real) (data float) (forward boolean))
(* y data))
0 #f ind 0 #f #f
(lambda (pos dur)
outer)))
|
will not save the "outer" declaration in the saved state file.
This is a general problem with save-state; there's no obvious way in Scheme to
save the current closure as text.
You can fix the saved state file by hand (it is just Scheme, Ruby, or Forth code, of course),
but that's not a very elegant solution.
The real limitation in using ptree-channel, however, arises from the fact that the read direction
can not only be backwards, but it can also change at any time. In conjunction with
the fragmentation during editing, this makes it
hard to use CLM generators, or anything that depends on previous samples.
Since the run macro (on which ptree-channel depends) is currently limited in
the kinds of vector or list elements it can decipher, you're pretty tightly
constricted in this context.
The read direction argument can be ignored if you know you're not going to read backwards.
The only hidden reverse read is in the src generator where a negative increment can be
generated in a variety of ways (for example, src driven by oscil). A one zero filter
could in this case be:
(ptree-channel (lambda (y data forward)
(let ((val (* 0.5 (+ y (data 0)))))
(set! (data 0) y)
val))
0 (frames) ind 0 #f #f ; "ind" is the sound
(let ((edpos (edit-position ind 0)))
(lambda (pos dur)
(vct (if (= pos 0) 0.0
(sample (- pos 1) ind 0 edpos))))))
|
See also virtual-filter-channel in snd-test.scm; it uses the optional third argument to the init function
to set up the filter state correctly, and has code to run the filter backwards if necessary.
Here are several more examples:
(define* (ptree-scale scl (beg 0) dur snd chn edpos)
"ptree-channel version of scale-channel"
(ptree-channel
(lambda (y)
(* scl y))
beg dur snd chn edpos #t))
(define* (ptree-xramp r0 r1 ubase (beg 0) dur snd chn edpos)
"exponential version of ramp-channel"
;; this is essentially what CLM's exponential envelope generator is doing
;; to accommodate C, it uses (exp (* power (log base))) prescaling power by (log base)
(let* ((base (if (> r0 r1) (/ 1.0 ubase) ubase)))
(ptree-channel
(lambda (y data forward)
(let* ((lr0 (data 0))
(lbase (data 1))
(incr (data 2))
(scl (data 3))
(power (data 4))
(val (* y (+ lr0 (* scl (- (expt lbase power) 1.0))))))
(if forward
(set! (data 4) (+ power incr))
(set! (data 4) (- power incr)))
val))
beg dur snd chn edpos #t
(lambda (frag-beg frag-dur)
;; r0, base, incr, (/ (- r1 r0) (- base 1.0)), current power
(vct r0
base
(/ 1.0 frag-dur)
(/ (- r1 r0) (- base 1.0))
(/ frag-beg frag-dur))))))
(define* (virtual-mix file beg snd chn)
(let ((len (mus-sound-frames file)))
(ptree-channel
(lambda (y state forward)
(declare (y real) (state sampler) (forward boolean))
(+ y (if forward (next-sample state) (previous-sample state))))
beg len snd chn -1 #f
(lambda (frag-beg frag-dur)
(make-sampler frag-beg file)))))
|
To handle an envelope in a ptree-channel application, it's probably
easiest to split it up into a sequence of ramps, each ramp keeping track
of its local begin point and increment. This is how sine-env-channel
works, not to mention the built-in linear and exponential envelopes.
The underlying procedure is any-env-channel
which needs only the segment connecting function, and handles the rest itself.
Here is an implementation of Anders Vinjar's power-envelope (a list
of breakpoints with an added base for each segment), that splits
the envelope into a sequence of xramp-channel
calls. It also uses as-one-edit to make the
result appear to be one operation in the edit history list.
(define* (powenv-channel envelope (beg 0) dur snd chn edpos)
;; envelope with a separate base for each segment:
;; (powenv-channel '(0 0 .325 1 1 32.0 2 0 32.0))
(let* ((curbeg beg)
(fulldur (or dur (frames snd chn edpos)))
(len (length envelope))
(x1 (car envelope))
(xrange (- (list-ref envelope (- len 3)) x1))
(y1 (cadr envelope))
(base (caddr envelope))
(x0 0.0)
(y0 0.0))
(if (= len 3)
(scale-channel y1 beg dur snd chn edpos)
(as-one-edit
(lambda ()
(do ((i 3 (+ i 3)))
((= i len))
(set! x0 x1)
(set! y0 y1)
(set! x1 (list-ref envelope i))
(set! y1 (list-ref envelope (+ i 1)))
(let* ((curdur (round (* fulldur (/ (- x1 x0) xrange)))))
(xramp-channel y0 y1 base curbeg curdur snd chn edpos)
(set! curbeg (+ curbeg curdur)))
(set! base (list-ref envelope (+ i 2)))))))))
|
If the init-func returns something other than a vct, you need to declare its type
for the run macro. There are several examples in snd-test.scm.
more ptree-channel examples:
smoothing: smooth-channel-via-ptree (examp.scm)
compander: compand-channel (examp.scm)
insert a block of arbitrary-valued samples: block-channel
sinusoidal ramp: sine-ramp (extensions.scm)
sinusoidal envelope: sine-env-channel (extensions.scm)
CLM-style contrast-enhancement: contrast-channel (extensions.scm)
add a constant to every sample: offset-channel (extensions.scm)
ring modulation: ring-modulate-channel (examp.scm)
delay by n samples (an experiment!): delay-channel in extensions.scm
dithering: dither-channel (extensions.scm)
FIR filter: virtual-filter-channel (examp.scm)
|
|
|
ramp-channel rmp0 rmp1 beg dur snd chn edpos
|
|
ramp-channel is a slight extension of scale-channel. It scales samples in the given sound/channel
between 'beg' and 'beg' + 'dur' by a (linear) ramp going from 'rmp0' to 'rmp1'.
|
|
read-only snd
|
|
This returns #t if 'snd' is read-only, #f otherwise. If you open
a file with view-sound, read-only is set to #t.
read-only does not reflect (or affect) the write permission state of the underlying file; it is a way
to keep from accidentally clobbering an otherwise writable file.
If it is #t (or if the file is not writable), a lock icon is displayed beside the file name.
|
|
redo edits snd chn
|
|
This re-activates 'edits' edits (the default is 1) in the given channel.
Redo follows the sync field if it
is not 0. The following might be a more reasonable redo function:
redo moves forward in the edit history list, whereas
undo backs up, and revert-sound resets the current
edit position to the start of the list.
For more about the edit history list, see Edit Lists.
In Ruby, redo is a part of the loop handling, so Snd's redo is renamed redo_edit.
redo-edit also exists in Scheme, for consistency.
|
|
report-in-minibuffer msg snd as-error
|
|
This posts 'msg' in the sound's minibuffer. The 'minibuffer' is the text widget between the sound's filename and the buttons
on the right, beneath the graph. It is intended to mimic Emacs' minibuffer, being useful mainly for short,
temporary messages. C-g clears it, as does clear-minibuffer.
If 'as-error' is #t, the message is placed in the minibuffer error label, rather than in the usual text area.
See also prompt-in-minibuffer.
|
|
reverse-channel beg dur snd chn edpos
|
|
reverse-channel is the regularized version of reverse-sound.
|
|
reverse-sound snd chn edpos
|
|
reverse-sound reverses the sound data in the given channel. There are some interesting non-causal effects you can get with this:
take a voice sound, reverse it, reverberate it, reverse it again, and you get the original with
reversed reverb. As a hack, you can reverse a sound (modulo a one sample rotation) by doing two ffts
(DSP-ers call this a "flip"):
|
|
revert-sound snd
|
|
This reverts 'snd' to its saved (unedited) state. A channel-specific version:
(define* (revert-channel snd chn)
(set! (edit-position snd chn) 0))
|
|
|
right-sample snd chn
|
|
This returns the position (in samples) of right edge of the time domain waveform. See left-sample,
move-one-pixel, and many examples in examp.scm.
|
|
run thunk
|
|
run is the Snd equivalent of the CL/CLM run macro. You can wrap it around a numerically-intensive
block of code, and the result will usually run 10 to 20 times faster. In the context of with-sound, run is used
to speed up instrument bodies. My timing tests indicate that Snd+Run instruments are within a factor of two to four
of the speed of CL+run+C in CLM. Currently, only s7 supports this optimization.
(define (ws-sine freq)
(let ((o (make-oscil freq)))
(run
(do ((i 0 (+ 1 i)))
((= i 100))
(outa i (oscil o))))))
In the past, the argument to run had to be a "thunk", a procedure of no arguments;
this style of use is still supported.
|
Functions embedded within run may need to declare the type of their arguments; run assumes each variable has one type (integer by default) throughout its life.
So, the following code
displays "0", rather than "3.14":
(run (let ((x 3.14)) (define (a b) (display b)) (a x)))
The "b" argument to "a" is assumed to be an integer, and passing in a float
causes nothing but confusion. To get this code to work right:
(run (let ((x 3.14)) (define (a b) (declare (b real)) (display b)) (a x)))
The current declarable types include any def-clm-struct type and:
int float boolean char string list symbol keyword vct sampler mix-sampler
sound-data clm float-vector int-vector vct-vector list-vector clm-vector
declare is modelled after Common Lisp's declare; it is specific to run.
The use of the run macro is hidden in many contexts: map-channel,
find-channel, etc. Internally the Snd run macro
uses 64-bit ints and doubles.
See optimization
for some timings. In Ruby, it's possible to use the RubyInline module instead.
|
|
sample samp snd chn edpos
|
|
This function gives the value of sample 'samp' in the given channel.
Scheme: (set! (sample 100) .5)
Ruby: set_sample(100, 0.5)
Forth: 100 0.5 set-sample
If the desired sample happens to fall outside the current buffer
for the indicated channel, sample grinds to a halt — if you're
running a loop through a bunch of samples, use the samplers
or channel->vct instead. 'samp' defaults to the current cursor location.
|
|
samples samp samps snd chn edpos
|
|
This returns a vct of 'samps' samples starting at 'samp' in the given channel.
'samp' defaults to 0. 'samps' defaults to frames - 'samp' (i.e. read to the end of the data).
'pos' is the edit history position to read (it defaults to the current position).
This is settable (as is sample):
:(samples 1000 10)
#<vct[len=10]: 0.033 0.035 0.034 0.031 0.026 0.020 0.013 0.009 0.005 0.004>
:(set! (samples 1000 10) (make-vct 10 .1))
#<vct[len=10]: 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.100>
|
|
save-sound snd
|
|
save-sound saves 'snd', writing the current state of the sound to its underlying sound file, (like the File menu's Save option).
save-hook is invoked upon save-sound. After save-sound, the sound has no undoable edits in its edit history
(this is different from Emacs, but I find Emac's way of handling this very confusing, and it's never what I want).
|
|
save-sound-as :file :sound :header-type :data-format :srate :channel :edit-position :comment
|
|
This saves 'sound' as 'file' (like the 'File:Save as' menu option). If 'channel' is specified,
only that channel is saved (it is extracted if necessary from the multichannel original).
'edit-position', if given, specifies which edit history position to save.
The :srate argument refers only to the new sound file's header's srate field;
the data is not resampled. If you want to resample the data as it is saved, see
the example under before-save-as-hook. If :data-format
is given, the sound file is written using that data format.
Any omitted argument's value
is taken from the sound being saved. save-sound-as returns the new file name.
(save-sound-as "test.snd" :data-format mus-bdouble :header-type mus-aifc)
saves the currently selected sound as an AIFC file using big-endian doubles for the samples.
To start a parallel editing branch on a given file, you could:
(save-sound-as "test.snd") (open-sound "test.snd")
To define an explicit channel extraction function:
Scheme:
(define (extract-channel filename snd chn) (save-sound-as filename snd :channel chn))
Ruby:
def extract_channel(filename, snd, chn) save_sound_as(filename, snd, :channel, chn) end
Forth:
: extract-channel { filename snd chn } filename snd :channel chn save-sound-as ;
The hooks called during a save operation are:
before-save-as-hook — can cancel the request or set its output parameters
save-hook
sound saved
if any sample is clipped during save, clip-hook
after-save-as-hook
|
|
scale-by scalers snd chn
|
|
scale-by scales the amplitude of 'snd' by 'scalers'. Unlike most of these functions,
scale-by follows the 'sync' buttons and affects all currently sync'd
channels. 'scalers' can be either a float, a list, or a vct.
In the latter case, the values are used one by one, applying each as
scale-by moves through the channels. If 'sync' is off, channel 'chn'
is scaled (it defaults to the currently selected channel). (scale-by 2.0) doubles all samples.
|
|
scale-channel scl beg dur snd chn edpos
|
|
scale-channel
scales (changes the amplitude) of a sound by 'scl'.
The multichannel version is scale-sound in frame.scm.
channel-polynomial is a generalization of the idea.
There are approximately a bazillion ways to scale samples in Snd; here's a potpourri of increasingly silly choices:
|
|
scale-to norms snd chn
|
|
scale-to normalizes 'snd' to 'norms' (following sync as in scale-by).
(scale-to 0.5) scales the current channel so that its maxamp is 0.5.
If all the sound's samples are 0.0, scale-to returns #f and does not perform any edit.
'norms' can be a number, a list of numbers, or a vct.
|
|
scan-chan func start end snd chn edpos
|
|
scan-chan applies 'func' to samples in the specified channel.
It is the old ("irregular") version of scan-channel.
|
|
scan-channel func beg dur snd chn edpos
|
|
scan-channel "scans" the data in the specified channel between the given sample numbers (the default
is the entire sound) by applying 'func' to each sample.
If 'func' returns something other than #f, the scan is halted,
and a list is returned
containing that value and the current sample position of the
scan. The following call scans the
current channel from sample 0 to the end looking for any sample greater than
.1:
>(scan-channel (lambda (y) (> y .1)))
(#t 4423)
In this case, we found such a sample at position 4423.
(define every-sample?
(lambda (proc)
(let ((baddy (scan-channel (lambda (y)
(not (proc y))))))
(if baddy (set! (cursor) (cadr baddy)))
(not baddy))))
>(every-sample? (lambda (y) (< y .5)))
#t
To scan all the channels of a multichannel sound in parallel, see
scan-sound.
In scan-chan, scan-channel, find, and count-matches (all the same underlying procedure), an attempt to jump back
into a previous call will not work. That is,
(let ((not-a-good-idea #f))
(scan-channel (lambda (y)
(call-with-current-continuation
(lambda (call)
(set! not-a-good-idea call)))
(> y .001)))
(not-a-good-idea))
|
will die with a segfault (this is fixable, with much effort and grumbling). If you want a continuable search, use a sampler:
(define reader #f)
(define last-proc #f)
(define (scan-again)
(if (sampler-at-end? reader)
#f
(let ((val (last-proc (reader))))
(if val
(list val (- (sampler-position reader) 1))
(scan-again)))))
(define (my-scan-chan proc)
(set! last-proc proc)
(set! reader (make-sampler 0))
(scan-again))
|
Now (my-scan-chan (lambda (y) (> y .1))) finds the first such sample, and
subsequent (scan-again) calls continue the search where the last call left off.
|
|
search-procedure snd
|
|
This gives the current global or sound-local (if 'snd' is specified) search procedure.
(set! (search-procedure) (lambda (y) (> y .1)))
|
|
selected-channel snd
|
|
This gives the selected channel in 'snd'; you can set it to select a channel. It returns #f is no channel is selected in 'snd'.
|
|
selected-sound
|
|
This returns the currently selected sound; you can set it to select a sound. It returns #f is there is no selected sound.
(or (selected-sound)
(and (not (null? (sounds)))
(car (sounds))))
returns the currently selected sound, if any, and failing that, any other sound that is currently open.
|
|
select-channel chn
|
|
This selects channel 'chn' in the currently selected sound;
equivalent to (set! (selected-channel) chn) .
See also select-channel-hook.
|
|
select-sound snd
|
|
This selects sound 'snd' (a sound object or an index); equivalent to (set! (selected-sound) snd) .
See also select-sound-hook.
|
|
set-samples samp samps data snd chn trunc edname infile-chan edpos auto-delete
|
|
set-samples (and its related (set! (samples...)...) form) set the given channel's samples starting from
sample 'samp' for 'samps' samples to the values in 'data'.
(set! (samples 0 100) (make-vct 100 .1))
(set-samples 0 100 (make-vct 100 .1))
both change all samples between 0 and 100 to be 0.1.
If 'samp' is beyond the end of the file, the file is first zero-padded to reach it.
'data' can be a filename.
(set-samples 10000 20000 "oboe.snd")
replaces 10000 samples with data from oboe.snd.
If 'data' is a vct, set-samples is identical to vct->channel.
If 'trunc' is #t and 'samp' is 0, the
sound is truncated (if necessary) to reflect the end of 'data'.
If the in-coming data file has more than one channel, 'infile-chan'
sets which input file to read. The in-coming data file is not deleted by Snd
unless 'auto-delete' is #t. (If you write a temporary sound
as an edit, it can be non-obvious when it is safe to delete that
file; 'auto-delete' set to #t asks Snd to handle cleanup).
The form (set! (samples samp samps 'snd chn trunc edname infile-chan edpos auto-delete') data) can also be used.
'env-sound-interp' in examp.scm has an example of the file version, using
sound-data objects and mus-sound-write to create a temporary file, but
it's probably simpler to use with-sound (see also linear-src-channel in dsp.scm):
|
|
short-file-name snd
|
|
This returns the brief (no directory) form of the sound's filename.
>(open-sound "oboe.snd")
#<sound 0>
>(file-name (integer->sound 0))
"/home/bil/cl/oboe.snd"
>(short-file-name (integer->sound 0))
"oboe.snd"
|
|
show-axes snd chn
|
|
This determines what axes are displayed.
If show-axes is show-all-axes (the default), both the x and y axes are displayed; if it is show-x-axis ,
just one (bottom) x axis is displayed, reducing screen clutter.
show-no-axes omits both x and y axes. To remove the x axis label, use
either show-x-axis-unlabelled or show-all-axes-unlabelled .
To omit all the x axis labels and ticks (but include the y axis as usual) use show-bare-x-axis .
This is the View:Axes choice.
|
|
show-grid snd chn
|
|
If show-grid is #t (the default is #f), a background grid is displayed (default is #f). See also grid-density.
|
|
show-marks snd chn
|
|
If show-marks is #t (the default), marks are displayed. This is the 'Show marks' View menu option.
|
|
show-mix-waveforms snd chn
|
|
If show-mix-waveforms is #t (the default), a mixed sound is displayed as a separate waveform above the main data. The rectangular tag
at the start of the waveform can be dragged to move the mix, or clicked to select it for the mix dialog.
|
|
show-sonogram-cursor snd chn
|
|
If show-sonogram-cursor is #t (the default is #f), the cursor is also displayed in the sonogram.
|
|
show-transform-peaks snd chn
|
|
If show-transform-peaks is #t (the default is #f), transform peak information is included in the transform display.
This is the 'peaks' button in the Transform options dialog.
|
|
show-y-zero snd chn
|
|
If show-y-zero is #t (the default is #f), the y=0 axis is displayed. This is the 'Show Y=0' View menu option.
|
|
smooth-channel beg dur snd chn edpos
|
|
smooth-channel is the regularized version of smooth-sound.
smooth-channel-via-ptree in examp.scm is the virtual form.
|
|
smooth-sound beg num snd chn
|
|
smooth-sound applies a smoothing function to the indicated data. This produces a sinusoid between
the end points:
(define (smoother y0 y1 num)
"go sinusoidally from y0 to y1 over num samps"
(let ((v (make-vct (+ 1 num)))
(angle (if (> y1 y0) pi 0.0))
(off (* .5 (+ y0 y1)))
(scale (* 0.5 (abs (- y1 y0)))))
(do ((i 0 (+ 1 i)))
((= i num) v)
(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
|
|
sound? returns #t if 'snd' refers to an open sound.
|
|
soundfont-info snd
|
|
This returns a list of lists describing 'snd' as a soundfont. Each inner list
consists of the sound name, start point, loop start, and loop end.
:(soundfont-info)
(("BrSteel_E4" 0 65390 65458) ("BrSteel_B2" 65490 131458 131637) ...)
To set a named mark at the start of each sound with un-named marks
at the loop points:
(define (mark-sf2)
(letrec
((sf2it
(lambda (lst)
(if (not (null? lst))
(let* ((vals (car lst))
(m1 (add-mark (cadr vals))))
(set! (mark-name m1) (car vals)))
(add-mark (caddr vals))
(add-mark (cadddr vals))
(sf2it (cdr lst))))))
(sf2it (soundfont-info))))
|
|
|
See also explode-sf2 in examp.scm.
|
|
sound->integer sound
|
|
This is the counterpart to integer->sound.
|
|
sound-loop-info snd
|
|
This gives info about loop points from the sound's header. The loop info is a list of
up to 4 points, the first two (start, end) refer to the sustain loop,
the second two to the release. The 5th and 6th list entries are the base note and detune values.
For historical reasons, the 7th and 8th entries are the sustain and release modes.
This is similar to mus-sound-loop-info (but it's settable). See explode-sf2 in examp.scm.
:(sound-loop-info)
(24981 144332 0 0 60 0 1 0)
|
|
sound-properties snd
|
|
This is a property list associated with the given sound. It is set to '() at the time a sound is opened. The accessor
is sound-property. There are several
examples of using it in snd-motif.scm and autosave.scm.
|
|
sound-property key snd
|
|
sound-property provides access to a sound's property list.
These properties are saved when Snd's state is saved (via save-state
or the Options:Save session menu). To omit a given property at that time, add its name (a symbol) to
the property 'save-state-ignore (a list of symbols); see 'inset-envelope in extensions.scm.
|
|
sounds
|
|
sounds returns a list of currently active sounds.
A common Snd trope is (map func (sounds)):
(map maxamp (sounds))
Or, if
the return value is not needed:
(for-each (lambda (snd) (display (short-file-name snd))) (sounds))
This can be
extended to provide a complete list of sounds and channels (since many Snd functions
take the "snd chn" arguments):
(define (all-chans)
(let ((sndlist '())
(chnlist '()))
(for-each (lambda (snd)
(do ((i (- (channels snd) 1) (- i 1)))
((< i 0))
(set! sndlist (cons snd sndlist))
(set! chnlist (cons i chnlist))))
(sounds))
(list sndlist chnlist)))
(apply map maxamp (all-chans))
|
|
|
spectrum-end snd chn
|
|
This is the amount of the frequency domain to include in the spectrum
display (the default is 1.0 = all of it).
spectrum-end the slider labelled '% of spectrum' in the View
Orientation dialog. See zoom-fft in examp.scm.
|
|
spectro-hop snd chn
|
|
This is the distance (in pixels) moved between successive spectrogram traces
(default is 4). spectro-hop is the 'hop' slider in the Color/Orientation dialog.
|
|
spectrum-start snd chn
|
|
This is the start point of the frequency domain in the spectrum
display (default is 0.0). See zoom-fft in examp.scm.
|
|
spectro-x-angle snd chn
|
|
This is the spectrogram x-axis viewing angle (the default is 90.0 except in GL where it is 300.0). See snd-gl.scm.
|
|
spectro-x-scale snd chn
|
|
This is the scaler (stretch amount) along the spectrogram x axis (the is default 1.0, in GL: 1.5).
|
|
spectro-y-angle snd chn
|
|
This is the spectrogram y axis viewing angle (the default is 0.0, in GL: 320.0).
|
|
spectro-y-scale snd chn
|
|
This is the scaler (stretch amount) for the spectrogram y axis (the default is 1.0).
|
|
spectro-z-angle snd chn
|
|
This is the spectrogram viewing angle for the z axis (the default is 358.0, in GL: 0.0).
|
|
spectro-z-scale snd chn
|
|
This is the scaler (stretch amount) for the z axis (the default is 0.1, in GL: 1.0).
|
|
squelch-update snd chn
|
|
This is #t if graphic updates are currently squelched (turned off). If you're doing a sequence of edits where intermediate
states aren't of great interest, you can save time by turning off redisplays.
(define (without-graphics thunk)
(set! (squelch-update) #t)
(let ((val (catch #t thunk (lambda args (car args)))))
(set! (squelch-update) #f)
val))
|
|
srate snd
|
|
This is the sound's sampling rate. If you set this to a new value, update-sound is called to
reflect the new srate, but any current edits are flushed. This is consistent
with the other header fields (data-format, etc), but it can be annoying.
There are several srates floating around in Snd.
(srate snd) returns the sampling rate of a particular (currently open) sound.
(mus-sound-srate filename) returns a sound file's sampling rate.
mus-srate is associated with the CLM package (setting the implicit srate for oscil etc).
default-output-srate is the default sampling rate used when opening new files.
enved-srate is a constant that can be assigned to the envelope editor's enved-target (to apply an envelope to the sampling rate).
region-srate is the sampling rate associated with a region.
|
|
src-channel num-or-env beg dur snd chn edpos
|
|
src-channel preforms sampling rate conversion using 'warped sinc interpolation'. The
argument 'num-or-env' can be a number, an envelope, or a CLM env generator.
(src-channel 2.0) makes the sound go twice as fast.
This is the regularized version of src-sound.
:(frames) ; current duration
50828
:(src-channel 2.0) ; make it half as long
2.0
:(frames)
25415
:(src-channel '(0 .5 1 1)) ; start slow and speed up
(0 0.5 1 1)
:(frames)
35235
:(src-channel (make-env '(0 .5 1 1) :length 20000)) ; stick at 1 after sample 20000
#<env linear, pass: 35236 (dur: 20000), index: 1, scaler: 1.0000, offset: 0.0000, ...>
:(frames)
42964
|
|
src-sound num-or-env base snd chn edpos
|
|
src-sound performs sampling rate conversion using 'warped sinc interpolation'. The
argument 'num-or-env', which sets the ratio between the old and the new srate, can be either a number or an envelope.
In the latter case, 'base' sets the segment base (the default is 1.0 = linear).
A value greater than 1.0 causes the sound to be transposed up.
A value less than 0.0 causes the sound to be reversed.
(src-sound 2.0) speeds the sound up by a factor
of 2 (transposes it up an octave), whereas (src-sound 0.5)
slows it down by the same factor (transposes it down an octave).
(src-sound '(0 1 1 2)) starts at the original speed, then
gradually increases until, at the end of the sound, it is going
twice as fast.
'num-or-env' can also be a CLM env generator (its duration should
be the same as the original sound, and its segments should not pass through 0.0). The following function can be used to predict
how long the resultant note will be given an src envelope:
;;; find new duration of sound after using env as srate.
;;; the envelope gives the per-sample increment, so the "tempo"
;;; is the inverse of that. To get the total new duration,
;;; we need to integrate the inverse envelope, but a straight
;;; line in the increment envelope becomes a 1/x curve in the
;;; tempo curve, so we use log(x) as integral of 1/x and
;;; take into account the local notion of "x".
(define (src-duration e)
(let* ((len (length e))
(ex0 (car e))
(ex1 (list-ref e (- len 2)))
(all-x (- ex1 ex0))
(dur 0.0))
(do ((i 0 (+ i 2)))
((>= i (- len 2)) dur)
(let* ((x0 (list-ref e i))
(x1 (list-ref e (+ i 2)))
(y0 (list-ref e (+ i 1))) ; 1/x x points
(y1 (list-ref e (+ i 3)))
(area (if (< (abs (- y0 y1)) .0001)
(/ (- x1 x0) (* y0 all-x))
(* (/ (- (log y1) (log y0))
(- y1 y0))
(/ (- x1 x0) all-x)))))
(set! dur (+ dur (abs area)))))))
;;; (src-duration '(0 1 1 2)) -> 0.693147180559945
:;; (src-duration '(0 1 1 .5)) -> 1.38629436111989
;;; (src-duration '(0 .5 .5 3 .6 1 .7 .1 .8 1.5 1 1)) -> 1.02474349685432
;;; here we're using this in the Snd listener:
>(frames)
220501
>(src-duration '(0 1 1 2))
0.693147180559945
>(src-sound '(0 1 1 2)) ; should be about .693 * 220500 frames
(0 1 1 2)
>(frames)
152842
>(/ 152842.0 220501)
0.693157854159392 ; tada!
|
The inverse, so to speak, of this is src-fit-envelope:
(define (src-fit-envelope e target-dur)
(scale-envelope e (/ (src-duration e) target-dur)))
|
|
start-playing chans srate background
|
|
If a play-list is waiting, this starts it. 'chans' defaults to 1,
'srate' defaults to 44100, 'background' defaults to #t. See play.scm or marks.scm.
|
|
start-progress-report snd chn
|
|
This starts a progress-report.
|
|
stop-player player
|
|
This removes 'player' from the current play-list (see make-player).
|
|
stop-playing snd
|
|
If 'snd' is playing, this stops it.
If no argument is given, it stops all playback. See play.scm, popup.scm,
stop-playing-hook, or stop-playing-selection-hook.
|
|
swap-channels snd1 chn1 snd2 chn2 beg dur edpos0 edpos1
|
|
This swaps the indicated channels, between 'beg' and 'beg' + 'dur'.
In simple cases, this is a virtual operation. swap-channels can be used to change channel order arbitrarily.
For example, the following function reverses the channel order:
Channel rotation is similar, though slightly more work; see scramble-channels in examp.scm.
Since swap-channels is a virtual operation in many cases, it's worth using it even
where just a channel copy is desired; mono->stereo
in extensions.scm for an example.
Another example is swap-selection-channels in examp.scm.
|
|
sync snd
|
|
sync returns the sound's 'sync' value (an integer, 0 = not sync'd). Several functions (scale-by, for example), apply to the
currently selected sound and also to any other sounds that share its sync value. (I later decided that
this was a bad idea, hence the regularized replacements). Sounds that share a given sync value
move together when you drag an x-axis slider and so on.
|
|
sync-max
|
|
This is the maximum sync setting seen so far — it provides a painless way to get a sync value that
is guaranteed to be unique. To sync all currently open sounds:
(let ((new-sync (+ 1 (sync-max))))
(for-each (lambda (snd) (set! (sync snd) new-sync)) (sounds)))
|
|
time-graph? snd chn
|
|
This is #t if the time domain graph is being displayed (the 'w' button).
|
|
time-graph-style snd chn
|
|
This determines how time-domain data is displayed.
The choices are:
graph-lines graph-dots graph-filled graph-lollipops graph-dots-and-lines
(set! (time-graph-style 0 4) graph-lollipops)
|
|
time-graph-type snd chn
|
|
If time-graph-type is graph-as-wavogram , the time domain waveform is displayed as a
'wavogram'.
The default is graph-once . See also wavo-hop and wavo-trace.
|
|
tracking-cursor-style snd chn
|
|
This is the cursor-style in effect when the cursor is tracking playback (with-tracking-cursor).
tracking-cursor-style can be cursor-cross or cursor-line . If you want some other shape,
use the function choice for cursor-style (that function's third argument can tell you when you're tracking).
|
|
transform-frames snd chn
|
|
This returns either 0 if there is no transform, transform-size if
transform-graph-type is graph-once ,
or (list spectrum-end time-slices fft-bins) if either a sonogram or a spectrogram is being displayed.
:(set! (transform-graph?) #t) ; turn on fft display
#t
:(transform-frames)
512
:(set! (transform-graph-type) graph-as-sonogram)
1 ; 1 = graph-as-sonogram
:(transform-frames)
(1.0 375 256) ; 1.0 -> full spectrum displayed
:(set! (transform-graph?) #f) ; turn off fft display
#f
:(transform-frames)
0
|
|
transform-graph? snd chn
|
|
This is #t if the given channel is displaying a spectrum (the 'f' button).
|
|
transform-graph-style snd chn
|
|
This determines how frequency-domain data is displayed.
The choices are:
graph-lines graph-dots graph-filled graph-lollipops graph-dots-and-lines
|
|
transform-graph-type snd chn
|
|
This determines the choice of spectral display. The choices are (default) graph-once (a single FFT),
graph-as-sonogram , and graph-as-spectrogram .
The sonogram is a set of FFTS taken at regular time intervals displayed as time vs frequency, using the
width or color of the spectral portion to indicate its amplitude. The spectrogram is similar, but uses
a 3D effect where the height of the line corresponds to its amplitude.
Currently, the fft-log-frequency and transform-normalization
choices are ignored by the spectrogram display.
If you've included openGL in Snd, the spectrogram will use openGL if with-gl is #t (the
default).
|
|
transform-normalization snd chn
|
|
This is the transform normalization choice (default: normalize-by-channel ).
If it is normalize-by-channel or normalize-by-sound , spectral data is
normalized to 1.0 before display. If dont-normalize , 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-globally (i.e. across all sounds).
|
|
transform-sample bin slice snd chn
|
|
This is the current value of the transform (if any) in 'bin' and (if a
sonogram or spectrogram) 'slice' in the given channel.
|
|
transform-size snd chn
|
|
This is the fft size (the default size is 512). It should be a power of 2.
If your version of Snd was built with FFTW, and you set transform-size
too large (on my machine, with 2 GBytes of memory,
(expt 2 26) is apparently too large), FFTW exits Snd! There is currently
no way to trap the error. Also, FFTW assumes the fft size is a (signed) int — 2^30 is probably
the transform-size limit.
|
|
transform-type snd chn
|
|
This is the spectrum transform type (the default is fourier-transform ).
fourier-transform wavelet-transform haar-transform
autocorrelation walsh-transform cepstrum
|
|
transform->vct snd chn v
|
|
This returns a vct with the transform data from the given channel.
If 'v' (a vct) is provided, it is filled, rather than creating a new vct.
See fft-peak for an example.
|
|
undo edits snd chn
|
|
This undoes 'edits' edits (the default is 1) in the given channel.
Undo follows the sync field if it
is not 0. The following might be a more reasonable undo function:
(define* (undo-channel (edits 1) snd chn)
(if (and snd (not (= (sync snd) 0)) chn)
(set! (edit-position snd chn)
(max 0 (- (edit-position snd chn) edits)))
(undo edits snd)))
See also undo-hook.
Since redo collides with Ruby, forcing me to change its name to redo_edit,
undo can also be accessed under the name undo_edit (in Scheme, undo-edit).
|
|
update-lisp-graph snd chn
|
|
This forces Snd to redisplay 'chn's' lisp graph. See enved.scm which uses the lisp graph as a local envelope editor.
|
|
update-sound snd
|
|
This causes Snd to update 'snd' (it re-reads the 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'.
See update-hook for a way to deal with the index confusion.
|
|
update-time-graph snd chn
|
|
This forces Snd to redisplay 'chn's' time domain graph. See color-samples in draw.scm.
|
|
update-transform-graph snd chn
|
|
This forces Snd to redisplay 'chn's' fft graph. For historical reasons, it also forces the current transform to completion.
|
|
variable-graph? index
|
|
This returns #t if 'index' refers to a variable graph (see make-variable-graph).
|
|
view-sound filename
|
|
This opens 'filename' read-only (you can edit the sound within Snd, but you can't overwrite the original sound).
|
|
verbose-cursor snd chn
|
|
If verbose-cursor #t (the default is #f), the cursor's position and other information is constantly
displayed in the minibuffer. This is the View:Verbose cursor option . The new name of this variable
is with-verbose-cursor;
eventually "verbose-cursor" will be retired.
|
|
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 around 48 choices, so this variable goes from
0 to 47 (the default is 0).
|
|
wavo-hop snd chn
|
|
This sets the distance upward (in pixels) between wavogram traces; that is,
the smaller this number, the more traces can be displayed (the default is 3). See time-graph-type.
|
|
wavo-trace snd chn
|
|
This sets the length (in samples) of each wavogram trace (the default is 64). See time-graph-type.
|
|
with-tracking-cursor snd
|
|
This is #t if the cursor is following along in the sound during playback. To make this the default:
(set! (with-tracking-cursor) #t)
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). In OSS, you can make a pretty good guess by setting
it to the number of fragments times half the size of the fragments (since these are bytes, and
cursor-location-offset is in terms of samples). If you have (mus-oss-set-buffers 4 12) ,
try (set! (cursor-location-offset) (* 4 (expt 2 11))) .
play from the current cursor position with a tracking cursor: pfc
display tracking cursor as a full height vertical line: tracking-cursor-style
track play once: control-click 'play'. (You can add a mark at the current tracking cursor location during the play with C-m)
leave the cursor at the final position after tracking play: if-cursor-follows-play-it-stays-where-play-stopped in examp.scm
tracking cursor accuracy: cursor-location-offset
tracking cursor updating: cursor-update-interval
|
|
|
with-verbose-cursor snd chn
|
|
If with-verbose-cursor is #t, the cursor's position and other information is constantly
displayed in the minibuffer. This is the View:Verbose cursor option
(default: #f). The old name of this variable was verbose-cursor.
|
|
x-axis-label snd chn context
|
|
This is the current x axis label. 'context' is one of time-graph (the default), lisp-graph , or
transform-graph .
:(x-axis-label)
"time"
:(set! (x-axis-label) "tempus")
"tempus"
|
|
x-axis-style snd chn
|
|
x-axis-style is the View menu 'X-axis units' option (the default value is x-axis-in-seconds ).
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), as a beat number (x-axis-in-beats ),
as a measure number (x-axis-in-measures ), or in digital clock format (DD:HH:MM:SS.ddd) (x-axis-as-clock , useful in
very large files).
When the x axis labelling is in measures, the label has the form M(B)F or M(B) where M is the one-based
measure number (that is, the first measure, at time 0.0, is measure 1), B is the one-based beat number
within that measure, and F (if present) is the location within that beat on a scale of 0.0 to 1.0.
If a major tick marks a measure beginning, and there are non-measure minor ticks, then the measure
is distinguished from the beat by having a longer tick mark.
|
|
x-bounds snd chn axis
|
|
This returns (list x0 x1) , the current x axis bounds in seconds. To display the entire sound:
(set! (x-bounds) (/ (frames) (srate)))
|
|
x->position x snd chn axis
|
|
This returns the graph (screen pixel) position that corresponds to the x axis value 'x'.
'axis' is one of time-graph (the default), lisp-graph , or transform-graph .
See draw.scm or gtk-popup.scm for examples.
|
|
x-position-slider snd chn
|
|
This is the value of x axis position slider. See zoom-fft in examp.scm.
|
|
x-zoom-slider snd chn
|
|
This is the value of x axis zoom slider. See zoom-one-pixel.
|
|
xramp-channel rmp0 rmp1 base beg dur snd chn edpos
|
|
xramp-channel is a slight extension of ramp-channel. It scales samples in the given sound/channel
between 'beg' and 'beg' + 'dur' by an exponential ramp going from 'rmp0' to 'rmp1' with the connecting segment curvature
set by 'base'.
(xramp-channel 0.0 1.0 32.0)
|
|
y-axis-label snd chn context
|
|
This is the current y axis label. 'context' is one of time-graph (the default), lisp-graph , or transform-graph .
|
|
y-bounds snd chn axis
|
|
This returns (list y0 y1) , the 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 (channels)))
(set! (y-bounds #f i) (list (- maxval) maxval))))
|
Or to set each channel to its own maxamp:
(do ((i 0 (+ 1 i)))
((= i (channels)))
(let ((maxval (maxamp #f i)))
(set! (y-bounds #f i) (list (- maxval) maxval))))
|
|
|
y->position y snd chn axis
|
|
This returns the graph (screen pixel) position that corresponds to the y axis value 'y'.
'axis' is one of time-graph (the default), lisp-graph , or transform-graph .
This is used in samples-via-colormap in draw.scm to draw the time domain samples in many colors.
|
|
y-position-slider snd chn
|
|
This is the value of y axis position slider. See zync in snd-motif.scm.
|
|
y-zoom-slider snd chn
|
|
This is the value of y axis zoom slider. See display-energy, or zync in snd-motif.scm.
|
|
zero-pad snd chn
|
|
zero-pad is the fft zero pad size as a multiple of the fft size; (set! (zero-pad) 1)
gives you half data, half zeros (the default value is 0). The data length is
determined by the nominal transform-size. Zero padding causes interpolation
of the fft points, making the display look smoother.
|
The control panel makes it easy to try out various sound effects without
editing anything. You can change volume ('amp'), pitch ('speed'), tempo
('expand'), reverb amount ('reverb'), simulated room size ('reverb len'),
brightness ('contrast'), and dullness ('filter'). To treat a current
setting as an edit operation, call apply-controls. For more on
the effects themselves (and a pretty picture!), see the discussion in snd.html.
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.
The control panel procedures are:
An edit list (in other editors this is called an "edit decision list", I guess because it sounds decisive)
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 omitted when you play the
channel, and so on. Instead, 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 just 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 via the 'pos' or 'edpos' arguments.
The edit list functions are:
It is sometimes more convenient to edit the edit history lists
directly, than to run Snd and invoke the "Save session" menu option.
These lists are Scheme, Ruby, or Forth 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:
Most of the transform functions and variables have been treated above, so they are only mentioned here.
The built-in dialogs, accessible from the main menu, provide the standard, but sometimes clumsy
ways to open and save sounds, edit envelopes and headers, and set various global variables.
In addition, many other dialogs are implemented in various Scheme/Ruby/Forth files.
The following
functions refer to the built-in dialogs. They were aimed originally at semi-internal needs like
saving the current Snd state, but might be useful elsewhere.
Functions such as color-orientation-dialog normally create and start the dialog in question; that is,
(color-orientation-dialog)
puts the color/orientation dialog on the screen. If you're trying instead to
customize the dialog in some way (in your initialization file, for example), you want the
dialog to be created (so that the various widget children exist), but don't want it to pop
up on the screen ('managed' in X jargon). So, most of the dialog functions have a 'managed' argument
that defaults to #t. If #f, the dialog is created, if need be, but not started.
install-searcher in snd-motif.scm, which adds customized file filtering code
to the File:Open dialog, first makes sure the dialog exists with (open-file-dialog #f)
.
add-directory-to-view-files-list dir dialog
|
|
This adds the sound files in directory 'dir' to the list of files in the View:Files dialog.
|
|
add-file-to-view-files-list file dialog
|
|
This adds 'file' to the list of files in the View:Files dialog.
|
|
add-file-filter name func
|
|
This adds 'func' to the file filter list under the name 'name'. The file filter list is a list
of functions, accessed from drop-down menus in the various file-related dialogs. Each such function
filters the list of files displayed by the dialog, so that only some interesting subset is posted.
The built-in filter is just-sounds which uses the sound file extension
tables to decide which files are sounds, omitting all others from the file lists.
You can add your own filters to this
menu with add-file-filter. The 'name' appears as the menu item label corresponding to the function.
The function should take one argument, a file name, and return #t to retain that file in the file list.
add-file-filter returns an integer to identify 'func' in other contexts.
(add-file-filter
"mono"
(lambda (a)
"filter out all but mono sound files"
(and (sound-file? a)
(= (channels a) 1))))
|
|
|
add-file-sorter name func
|
|
This adds 'func' to the file-sorter list under the name 'name'. Some dialog file lists include
a "sort" menu to reorder the files in the file list. You can add your own sort functions to this
menu with add-file-sorter. The 'name' appears as the menu item label corresponding to the function.
The new sorter's index is returned; it is an integer for use with functions such as view-files-sort.
The function should take two arguments, each a filename, and return a strcmp-like number describing
how to sort the pair. The following adds a sorter named "duration" that sorts files from shorter
to longer:
(add-file-sorter
"duration"
(lambda (a b)
"sort by duration from short to long"
(let ((dur1 (mus-sound-duration a))
(dur2 (mus-sound-duration b)))
(if (> dur1 dur2) 1
(if (< dur1 dur2) -1 0)))))
|
|
|
add-sound-file-extension ext
|
|
This adds 'ext' to the list of (case sensitive) sound file extensions used by sound-files-in-directory.
The initial list is ("snd" "aiff" "aif" "wav" "au" "aifc" "voc" "wve" "WAV" "sf2" "rf64" "caf").
To add "ogg" as a recognized extension:
(add-sound-file-extension "ogg")
The list itself is sound-file-extensions.
See also add-sound-file-extension-1.
|
|
add-to-main-menu menu-label update-callback
|
|
This adds a new top-level menu named 'menu-label' and returns its menu index. The index
identifies the menu for add-to-menu and others.
'update-callback' is a procedure of no arguments that is
called each time the menu is displayed.
Scheme:
(define new-menu (add-to-main-menu "New Menu"))
(add-to-menu new-menu "First Item" (lambda () (snd-print ";item 1")))
(add-to-menu new-menu "Second Item" (lambda () (snd-print ";item 2")))
Ruby:
new_menu = add_to_main_menu("New Menu")
add_to_menu(new_menu, "First Item", lambda do | | snd_print("item 1") end)
add_to_menu(new_menu, "Second Item", lambda do | | snd_print("item 2") end)
Forth:
"New Menu" add-to-main-menu constant new-menu drop
new-menu "First Item" lambda: <{ }> "item1" snd-print ; undef add-to-menu drop
new-menu "Second Item" lambda: <{ }> "item2" snd-print ; undef add-to-menu drop
|
|
add-to-menu top-menu menu-label callback position
|
|
This adds the menu 'menu-label' to the top-level menu whose index is
'top-menu' with the callback function 'callback', then returns the new menu label widget.
The built-in
Snd menus are numbered from 0 ('File') to 5 ('Popup'); 'Help' is menu 4. If the 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 new-effects.scm for many examples.
(add-to-menu 1 "Stop Playing"
(lambda () (stop-playing)))
(add-to-menu 5 "Reduce height"
(lambda () (set! (window-height) (/ (window-height) 2))))
|
|
|
|
channel-widgets
|
|
channel-widgets returns a list of various widgets associated with a given channel:
0: graph ;drawing area for all 3 graphs (time, fft, lisp)
1: w-button
2: f-button
3: x-position slider
4: y-position slider
5: x-zoom slider
6: y-zoom slider
7: edit history list
8: right(united-chans) y-position slider
9: right y-zoom slider
10: main pane for channel
------ the rest only in Gtk+
11..16: adjustment widgets associated with the zoom and position sliders
|
|
clear-listener
|
|
This deletes all listener text from the beginning to the cursor position (C-M-g is bound to this function).
|
|
color-orientation-dialog managed
|
|
This creates and (if 'managed' which defaults to #t) activates the Color/Orientation dialog; it returns the dialog widget.
|
|
define-envelope name data (base 1.0)
|
|
This adds an envelope to the envelope editor's list, under the name 'name', using the list of breakpoints
'data', and the optional 'base'. If the 'base' is omitted, this is the same as defvar.
Scheme: (define-envelope ramp '(0 0 1 1))
Ruby: define_envelope("ramp", [0, 0, 1, 1])
Forth: $" ramp" '( 0.0 0.0 1.0 1.0 ) 1.0 define-envelope
|
|
defvar var val
|
|
This is the same as (define var val) except that the envelope editor keeps track
of 'var' thereafter and treats lists as envelopes.
defvar exists in this context so that Snd and CLM can share envelope files.
(defvar a-func '(0 0 1 1))
|
|
delete-file-filter index
|
|
This removes the file filter function associated with 'index' from the file filter list.
|
|
delete-file-sorter index
|
|
This removes the file sorter function associated with 'index' from the file sorter list.
|
|
dialog-widgets
|
|
dialog-widgets returns a list of dialog widgets (or lists thereof, or #f if none yet):
0: View: Color/Orientation dialog
2: Edit: EnvelopeEditor dialog
3 and 4: unused
5: Options: Transform dialog
6: File: Open dialog
7: File: Save as dialog
8: View: Files dialog
9: raw data dialog (activated when raw sound opened, sometimes)
10: File: New sound dialog
11: File: Mix dialog
12: Edit: Edit header dialog
13: Edit: Find dialog
14: Help dialog
16: View: Mixes dialog
17: File: Print dialog
18: File: Recorder dialog
19: View: Regions dialog
20: info dialog (activated by info-dialog function)
22: Edit: Selection Save as dialog
23: File: Insert file dialog
24: region save as dialog (from regions dialog save button)
25: Options: Preferences dialog
|
|
edit-header-dialog snd
|
|
This starts the Edit Header dialog on 'snd', returning the dialog widget.
|
|
enved-base
|
|
This is the envelope editor exponential base value.
|
|
enved-clip?
|
|
This reflects the state of the envelope editor 'clip' button.
|
|
enved-dialog
|
|
This starts the envelope editor dialog, returning the dialog widget.
|
|
enved-envelope
|
|
This is the envelope (a list of breakpoints) in the envelope editor's graph window.
|
|
enved-filter
|
|
This reflects the type of the envelope editor's filter (the default #t means FIR; #f is FFT). To get the FFT display in the envelope editor
as the default:
(set! (enved-filter) #f)
(set! (enved-wave?) #t)
(set! (enved-target) enved-spectrum)
|
enved-filter-order
|
|
This is the order of the envelope editor's FIR filter (the default is 40).
|
|
enved-in-dB
|
|
This reflects the state of the envelope editor 'dB' button (it defaults to #f).
|
|
enved-power
|
|
This is the envelope editor's base scale range (it defaults to 3.0).
|
|
enved-style
|
|
This is the envelope editor choice for connecting breakpoints. It can be envelope-linear (the default), or
envelope-exponential .
|
|
enved-target
|
|
This determines how the envelope editor's current envelope is applied to the selected data.
The choices are enved-amplitude , enved-srate and enved-spectrum .
The first treats the envelope as an amplitude envelope, the second as an srate curve (changing speed),
and the last as a frequency response envelope for a filter.
|
|
enved-waveform-color
|
|
This is the color of the waveform displayed in envelope editor (the default is blue).
|
|
enved-wave?
|
|
This reflects the state of the envelope editor 'wave' button.
The wave shown is the time domain display, even when filtering.
|
|
find-dialog managed text
|
|
This creates and (if 'managed' which defaults to #t) starts the Edit:Find dialog, returning the dialog widget.
|
|
focus-widget widget
|
|
This gives 'widget' "focus" — it becomes the active widget, receiving keystrokes and so on.
|
|
gl-graph->ps file (type 0) snd chn
|
|
This creates a Postscript picture of the current openGL display in snd's channel chn (a spectrogram).
'file' defaults to eps-file.
'type' can be 0: eps, 1: ps, 2: pdf, 3: tex, 4: svg, or 5: pgf.
This function is available only if OpenGL and gl2ps have been loaded (via the --with-gl and
--with-gl2ps configuration switches).
|
|
goto-listener-end
|
|
This moves the cursor to the end of the listener text, and scrolls the window so that it is visible.
|
|
graph->ps file
|
|
This creates a Postscript picture of the current display.
'file' defaults to eps-file.
See also eps-bottom-margin, eps-left-margin,
and eps-size.
|
|
help-dialog subject help-string xrefs urls
|
|
This starts the help dialog with the title 'subject' and help area text 'help', returning the dialog widget.
'xrefs' is an optional list of strings to post in the "related items" list. 'urls' is a corresponding
list of urls.
(help-dialog "xyzzy" "are we having fUn?")
There are many examples in new-effects.scm.
(defmacro with-snd-help (form)
;; if an error occurs while evaluating form, try to start the help dialog with some relevant help
`(catch #t
(lambda ()
,form)
(lambda args
(if (and args (cadr args) (string? (cadr args)))
(let* ((func (if (string=? "set!" (substring (cadr args) 0 4))
(substring (cadr args) 5)
(cadr args)))
(help (snd-help func)))
(if help (help-dialog func help))))
args)))
|
|
|
hide-widget widget
|
|
This hides (unmanages) 'widget'.
To remove the y-position slider (which is only there
for looks):
(hide-widget (list-ref (channel-widgets) 4))
|
|
info-dialog subject info
|
|
This starts the info dialog with the title 'subject' and body 'info' returning the dialog widget.
|
|
insert-file-dialog managed
|
|
This creates and (if 'managed' which defaults to #t) activates the File:Insert dialog, returning the dialog widget.
|
|
listener-color
|
|
This is the background color of listener.
(set! (listener-color) (make-color 0 0 0))
|
|
listener-font
|
|
This is the listener font.
|
|
listener-prompt
|
|
This is the listener prompt which defaults to ">". I like ":" better (as you can see in many of the examples in this file),
so in ~/.snd_s7 I have this line:
(set! (listener-prompt) ":")
|
|
listener-selection
|
|
listener-selection returns the currently selected text in the listener, or #f if there isn't any. The following code
starts the help dialog with help related to the selection if "h" is typed in the graph:
(bind-key #\h 0
(lambda ()
"start help dialog based on listener selected text"
(let ((subject (listener-selection)))
(if subject
(help-dialog subject (snd-help subject))))))
But it's probably more convenient to use the listener-click-hook
and click-for-listener-help (draw.scm).
|
|
listener-text-color
|
|
This is the text color in the listener. For red text on a black background:
(set! (listener-color) (make-color 0 0 0)) ; in Gtk, maybe a bad idea — the cursor remains black...
(set! (listener-text-color) (make-color 1 0 0))
|
|
main-menu menu
|
|
main-menu returns the top-level menu
associated with its integer argument:
0: File menu
1: Edit menu
2: View menu
3: Options menu
4: Help menu
5: built-in popup menu
and others as added by add-main-menu
|
|
main-widgets
|
|
main-widgets returns a list of the top-level widgets in Snd (#f if not created):
0: top-level-application ; XtAppContext in Motif, top level window in Gtk+
1: top-level-shell
2: main-pane ; outer paned window top window (holds sounds)
3: main-sound-pane
4: listener-pane ; outer paned window bottom window
5: notebook-outer-pane
For example, to get at Snd's main shell widget:
Scheme: (cadr (main-widgets))
Ruby: main_widgets.cadr
Forth: main-widgets cadr
|
|
menu-widgets
|
|
menu-widgets returns the top-level menu widgets (cascade menus in Motif or menu bars in Gtk+)
as a list:
0: top-level-menu-bar
1: file-menu
2: edit-menu
3: view-menu
4: options-menu
5: help-menu,
6: default popup menu
See snd-motif.scm, snd-gtk.scm, kmenu.scm, and new-effects.scm for various examples.
Manipulating menus can be tricky in both Motif and Gtk; if I were to try to explain
submenus and whatnot here, I'd only get tangled up in half-forgotten complications.
When I have to deal with this stuff, I always go to a working example.
|
|
mix-dialog-mix
|
|
This is the id (mix object) of the mix displayed by the mix dialog.
|
|
mix-file-dialog managed
|
|
This creates and (if 'managed' which defaults to #t) activates the File:Mix dialog,
returning the dialog widget.
|
|
new-sound-dialog managed
|
|
This creates and (if 'managed' which defaults to #t)
starts the File:New sound dialog, returning the dialog widget.
|
|
open-file-dialog managed
|
|
This creates and (if 'managed' which defaults to #t) activates the File:Open dialog,
returning the dialog widget.
If the xm module is loaded, we could add our own info via:
(let* ((dialog (open-file-dialog #f))
(rc (find-child dialog "info-rc2")) ; holds the info labels if a sound file is selected
(label (XtCreateManagedWidget "file-preview-info" xmLabelWidgetClass rc
(list XmNbackground (highlight-color)))))
(XtAddCallback (XmFileSelectionBoxGetChild dialog XmDIALOG_LIST)
XmNbrowseSelectionCallback
(lambda (widget context info)
(let ((file (cadr (XmStringGetLtoR (.item info) XmFONTLIST_DEFAULT_TAG))))
(XtVaSetValues label
(list XmNlabelString
(XmStringCreateLtoR (format #f "~A: no comment" file)
XmFONTLIST_DEFAULT_TAG)))))
#f))
|
|
|
preferences-dialog
|
|
This activates the Options:Preferences dialog.
|
|
print-dialog managed direct-to-printer
|
|
This creates and (if 'managed' which defaults to #t) activates the File:Print dialog, returning the dialog widget.
|
|
recorder-dialog
|
|
This starts the recorder window, returning the dialog widget.
|
|
remove-from-menu top-menu menu-label
|
|
This removes the menu 'menu-label' from the top top-level menu whose index is 'top-menu'. See examp.scm or snd-motif.scm.
|
|
reset-listener-cursor
|
|
This resets the listener cursor to the default pointer shape.
|
|
save-envelopes filename
|
|
This saves the envelope editor envelopes in 'filename'.
|
|
save-listener filename
|
|
This saves the listener contents in 'filename'.
|
|
save-region-dialog managed
|
|
This creates and (if 'managed' which defaults to #t)
starts the Region Save-as dialog (to save the current Region browser region), returning the dialog widget.
|
|
save-selection-dialog managed
|
|
This creates and (if 'managed' which defaults to #t)
starts the Edit:Save selection as dialog (to save the current selection), returning the dialog widget.
|
|
save-sound-dialog managed
|
|
This creates and (if 'managed' which defaults to #t)
starts the File:Save as dialog (to save the currently selected sound), returning the dialog widget.
|
|
show-listener
|
|
If show-listener is set to #t, Snd opens the listener pane; otherwise it closes the listener.
|
|
show-widget widget
|
|
This shows (manages) 'widget'.
|
|
sound-file-extensions
|
|
This is the list of sound file extensions used by the "just-sounds" buttons and
sound-files-in-directory to try to recognize
sound files. It is settable: a list of extensions as strings:
(set! (sound-file-extensions)
(list "snd" "aiff" "aif" "wav" "au" "aifc" "voc" "wve" "WAV" "sf2" "rf64" "caf"))
|
|
sound-file? filename
|
|
This returns #t if 'filename' has an extension that matches one in the sound-file-extensions list.
:(sound-file? "oboe.snd")
#t
:(sound-file? "extsnd.html")
#f
|
|
sound-files-in-directory dir
|
|
This returns a list of the sound files found in 'dir'. A file is considered a sound if it has data 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
prints the names of all the stereo AIFC files it finds:
See also map-sound-files in extensions.scm.
|
|
sound-widgets
|
|
sound-widgets returns a list of various widgets specific to a given sound:
0: main-pane
1: name-label ; sound's file name
2: control-panel
3: minibuffer
4: play button
5: filter-graph ; control panel drawing area for filter envelope
6: unite button ; invisible in mono sounds
7: minibuffer-label ; prompt area for the minibuffer
8: name-icon ; hour-glass or whatever
9: sync button
For example, we can read and write the minibuffer:
Scheme:
:(report-in-minibuffer "this is a test")
"this is a test"
:(widget-text (list-ref (sound-widgets) 3))
"this is a test"
Ruby:
:report_in_minibuffer("this is a test")
this is a test
:widget_text(sound_widgets()[3])
this is a test
Forth:
snd> "this is a test" report-in-minibuffer
this is a test
snd> 0 sound-widgets 3 list-ref widget-text
this is a test
|
|
transform-dialog managed
|
|
This creates and (if 'managed' which defaults to #t) activates the Options:Transform dialog, returning the dialog widget.
|
|
view-files-amp dialog
|
|
This is the value of the amplitude slider in the View:Files dialog.
|
|
view-files-amp-env dialog
|
|
This is the amplitude envelope displayed in the View:Files dialog.
|
|
view-files-dialog managed
|
|
This creates and (if 'managed' which defaults to #t) activates a
View:Files dialog and returns the dialog widget.
|
|
view-files-files dialog
|
|
This is the file list (a list of strings) of a View:Files dialog.
|
|
view-files-selected-files dialog
|
|
This is the list of selected files (a list of strings) in a View:Files dialog.
|
|
view-files-sort dialog
|
|
This is the sort function choice in a View:Files dialog. Initially there are 6 sort choices: a..z, z..a (sort by file name),
new..old, old..new (sort by file write date), and small..big, big..small (sort by file size). The default is 0 (a..z).
If you set view-files-sort without giving the dialog argument, it just affects the startup state of subsequent new View:Files
dialogs. To set the sort choice in the current dialog:
(set! (view-files-sort (list-ref (dialog-widgets) 8)) 2) ; 2=new..old
|
|
view-files-speed dialog
|
|
This is the value of the speed slider in a View:Files dialog.
|
|
view-files-speed-style dialog
|
|
This is the speed style choice in a View:Files dialog. It is one of speed-control-as-float (the default),
speed-control-as-ratio , or speed-control-as-semitone .
|
|
view-mixes-dialog
|
|
This creates and activates the View:Mixes Dialog, returning the dialog widget.
|
|
view-regions-dialog
|
|
This starts the region browser (a no-op if there are no regions), and returns the dialog widget.
|
|
widget-position widget
|
|
This returns a list giving the widget's x and y coordinates (in pixels). It can be set to reposition the widget.
See nb.scm where it uses the current window position to try to find a convenient place for the help dialog.
|
|
widget-size widget
|
|
This returns a list giving the widget's width and height (in pixels). It can be set to resize the widget. See nb.scm and examp.scm.
(set! (widget-position (cadr (main-widgets))) (list 300 100))
|
|
widget-text widget
|
|
This returns the text widget's text. It can be set.
|
|
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 sometimes appends a stack trace. So, as an example, selection-position
throws 'no-active-selection if there isn't a selection:
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:
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 has a number
of errors such as 'out-of-range, 'wrong-type-arg, 'numerical-overflow,
etc. The Snd-specific errors are:
You can use these errors in your code, if you like, or add your own. The following
throws the error 'no-such-file:
There is one special catch: 'snd-top-level. This is used by the
debuggers to exit the current context, returning up a level in the
stack of listeners. Normally that means you jump out of a breakpoint
or whatever and find yourself back at the top level. (throw 'snd-top-level)
.
This catch is also the target of the SIGUSR1 signal, so if you're caught in an infinite
loop and Snd won't respond to C-g or anything else, go to a shell, get the Snd
process number ("PID" in the 'ps au' printout), then: kill -10 6141. SIGUSR1 is
signal 10, and we're using the kill program to send that signal to Snd (process 6141 in our example).
When s7 hits an error, it prints out a stacktrace as well as the error message.
The variable *error-info* has additional info. You can also trace functions, and
place breakpoints. See s7.html for further details.
See the debugging section in the fth documentation.
If you hit a bug in Snd's C code, you'll need to use gdb
to track it down, or mail me the gory details;
if the error is a segfault, there is probably a file named "core" or "core.nnnn"
on the current directory:
The "where" command displays the stack at the point of the error.
"up", and "down" move around in the stack, and "info locals" prints out
the current frame's variables. If it's not a segfault, you can
Then get the error to happen, at which point you should fall into gdb
where you can type "where" and so on. If the problem involves X, you
may need to run -sync. If Gtk, run --g-fatal-errors. If Snd gets hung
and you need to type C-C to get out,
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:
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. The overall
widget background color is basic-color.
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-*-*-*-*-*-*".
In Gtk, the font names resemble "Monospace 10", etc.
The font variables are:
It is possible to draw directly on any of the channel graphs. Examples include the
show-original after-graph-hook function, and the
x-cursor function that draws an "x" shaped cursor.