In the Motif version, there are a few X-style resources that Snd explicity looks for (see Snd.ad):
initFile "~/.snd" epsFile "snd.eps" overwriteCheck 0 autoResize 1 horizontalPanes 0 buttonFont -*-times-medium-r-*-*-14-*-*-*-*-*-iso8859-1 boldbuttonFont -*-times-bold-r-*-*-14-*-*-*-*-*-iso8859-1 axisLabelFont -*-times-medium-r-normal-*-20-*-*-*-*-*-iso8859-1 axisNumbersFont -*-courier-medium-r-normal-*-14-*-*-*-*-*-iso8859-1 helpTextFont 9x15 listenerFont default useSchemes none highlightcolor ivory1 basiccolor ivory2 positioncolor ivory3 zoomcolor ivory4 cursorcolor red selectioncolor lightsteelblue1 mixcolor lightgreen mixfocuscolor yellow2 listenercolor aliceblue envedwaveformcolor blue filterwaveformcolor blue mixwaveformcolor darkgray graphcolor white selectedgraphcolor white datacolor black selecteddatacolor black markcolor red pushedbuttoncolor lightsteelblue1 sashcolor lightgreen
If you have the HTML widget loaded, the following resources are also available:
htmlDir "." ! also the variable html-dir htmlWidth 600 htmlHeight 400 htmlFontSizeList "14,10,24,24,18,14,12" htmlFixedFontSizeList "14,10"
You can experiment with other choices by using the -xrm command line argument:
snd -xrm '*Highlightcolor: Red' oboe.snd snd -xrm '*AxisNumbersFont: 6x10' oboe.snd snd -xrm '*overwriteCheck: 1' oboe.snd snd -xrm '*useSchemes: all' -xrm '*scheme: Pacific' snd -xrm '*fontList: 9x15' oboe.snd snd -xrm '*listenerFont: 6x10' oboe.snd snd -xrm '*mixwaveformcolor: red' oboe.snd -notebook snd oboe.snd pistol.snd -xrm '*selectedgraphcolor: black' -xrm '*selecteddatacolor: white' snd oboe.snd -title hiho -display hummer.hiho:0.0 -xrm '*chn-graph*backgroundPixmap: text.xpm'
The color names can be found in rgb.scm. If you use SGI color schemes (the useSchemes resource), most of the color resources mentioned above are ignored (the cursor and selection colors are never ignored). If color schemes are available they're listed in /usr/lib/X11/schemes, probably -- it's unfortunate that there is the language Scheme used by Guile, and the notion of an SGI color scheme -- there is no connection between the two. The last example sets the window title to "hiho", rather than "snd", displays the window on the machine hummer.hiho (presumably accessible over the net), and tiles the graph backgrounds with the contents of text.xpm. To get the -geometry argument to work, set the autoResize resource to 0:
snd oboe.snd -geometry 800x200 -xrm '*autoResize: 0'
These resources can be set in your .Xdefaults file:
snd*buttonFont: -adobe-times-medium-r-*-*-14-*-*-*-*-*-*-* snd*boldbuttonFont: -adobe-times-bold-r-*-*-14-*-*-*-*-*-*-* snd*axisLabelFont: -adobe-times-medium-r-normal-*-18-*-*-*-*-*-*-* snd*axisNumbersFont: 9x15 snd*fontList: 9x15 snd*helpTextFont: 9x15
The autoResize resource determines how Snd acts when files are added or removed from its overall display. The default (1) causes Snd to expand or contract the main window's size to accomodate the sounds (many people find this distracting); if autoResize is 0, the outer window size remains the same, and the sounds try to fit as best they can. See also the variable auto-resize. If overwriteCheck is 1, Snd asks before overwriting existing files. The horizontalPanes resource is equivalent to the -h flag; if 1, sounds are layed out horizontally rather than vertically; if 2, you get a notebook widget holding the sounds.
The various color resources are:
basiccolor default background color everywhere; basic-color cursorcolor color of the cursor; cursor-color datacolor unselected data color; data-color envedwaveformcolor color of envelope editor waveform; enved-waveform-color filterwaveformcolor color of control panel filter waveform; filter-waveform-color graphcolor unselected channels' graph background; graph-color highlightcolor highlighting here and there; highlight-color listenercolor background color of the listener; listener-color markcolor color of the mark indicator; mark-color mixcolor used for mixer console titles; mix-color mixfocuscolor mix selected indicator; mix-focus-color mixwaveformcolor color of mix waveform data; mix-waveform-color positioncolor color of position sliders; position-color pushedbuttoncolor color of pushed button; pushed-button-color sashcolor color of paned window sash handles sash-color selecteddatacolor color of the data in selected channel; selected-data-color selectedgraphcolor background of selected channel's graph; selected-graph-color selectioncolor color of an active selection; selection-color textfocuscolor color of text field with focus; text-focus-color zoomcolor color of zoom sliders; zoom-color
Each of these colors can be set in Guile using the second name given above ("basic-color").
Colors are defined by calling make-color with the three red/green/blue values,
each a float between 0.0 and 1.0. (set! (basic-color) (make-color 1.0 0.0 0.0))
sets
the overall background color of Snd to red. rgb.scm defines all the standard X11 color names
(you probably don't want to load the whole thing; just use the names as needed).
There are several other resources that set various widget sizes: zoomSliderWidth, positionSliderWidth, toggleSize, sashSize, sashIndent, channelSashSize, channelSashIndent, and envedPointSize. And several more color resources: whitecolor (list background), blackcolor (recorder VU meter text), redcolor (buttons, VU clipping, etc), greencolor (a few buttons), yellowcolor (a few envelope editor buttons), lightbluecolor (the recorder), and lighterbluecolor (the fft option panel).
The following flags are recognized by Snd (leaving aside all the usual Xt/X-related flags like -xrm).
-h -horizontal layout sounds as horizontal panes -v -vertical layout sounds vertically (the default) -notebook layout sounds in a notebook widget (Motif 2.0 or later) -separate layout sounds each in a separate window (lisp listener in main window) --help print some help, version info, and exit --version print version info -noglob don't read SND_CONF, if any -noinit don't read ~/.snd, if any -p -preload <dir> preload sound files in directory <dir> (for example, snd -p .) -l -load <file> load guile (scheme) code in <file> (for example, snd -l test.scm) -e -eval expr evaluate expr
The -e switch evaluates its argument as though it had been passed to M-X. The initialization file, if any, is loaded first, then the arguments are processed in order. For example
snd -e "(set! (data-color) (make-color 1 0 0))" oboe.snd
reads ~/.snd, if any, then sets the (unselected) data color to red, then opens oboe.snd.
./snd -eval '(begin (display (+ 1 2)) (exit))'
prints "3" and exits. The "-title" argument works in both versions of Snd.
When Snd starts up, it looks for an "initialization file", normally named "~/.snd" (its name can be set via the X resource mechanism, or through the environment variable SND_INIT_FILE). This optional file is supposed to be just like emacs' .emacs file, containing any customizations or extensions that you want loaded whenever Snd starts up. For example, say we want the Snd window to start out 800x500, want to predefine an envelope named "env1", and want the file selection box to default to showing just sound files. We make ~/.snd and put in it:
(set! (window-width) 800) (set! (window-height) 500) (defvar env1 '(0 0 1 1 2 0)) (set! (just-sounds) #t)
In addition, we could add our own analysis functions or whatever. In more complex situations, you may want an initialization file particular to a given machine, and global across users; in that case, the macro SND_CONF gives the name of this global initialization file. At ccrma, it's "/etc/snd.conf". The global file is read before the user's local file; both can, of course, be absent. To override reading the global init file when Snd is invoked, include the switch -noglob. To override the local init file, use -noinit. To set the global file name in a makefile include (for example) -DSND_CONF='"/home/bil/cl/sndconf"' in CFLAGS.
As a more extended example, here is my initialization file (or, I wish it were this neat; the actual file is more full of junk than my garage):
(use-modules (ice-9 popen) (ice-9 debug)) (set! (window-width) 500) (set! (window-height) 300) (set! (window-y) 50) (set! (window-x) 300) (defvar env1 '(0 1 1 2 2 1)) (set! (show-mix-waveforms) #t) (define shell (lambda (cmd) (let* ((str "") (fil (open-pipe cmd "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) str))) (define beige (make-color 0.96 0.96 0.86)) (define blue (make-color 0 0 1)) (set! (selected-graph-color) beige) (set! (selected-data-color) blue)
It is possible to load your own C code into Snd at run-time or use any external program from within Snd as an editing function. And, perhaps most useful, you can run Snd as an Emacs subjob.
Snd watches stdin; any input received is evaluated as if typed in Snd's lisp listener; any subsequent output is sent to stdout; presumably any process could communicate with Snd in this manner. But the intention here was to connect to Emacs via ILISP (available at http://ilisp.cons.org, and possibly built into Xemacs). Once you have ILISP, put this code in your .emacs file:
(require 'ilisp) (defdialect snd "Snd" ilisp (setq ilisp-block-command "(begin \n%s)") (setq ilisp-load-command "(load \"%s\")") (setq ilisp-init-hook '((lambda () (ilisp-init nil nil nil)))) (setq comint-prompt-regexp "^>+") (setq ilisp-directory-command "(getcwd)" ilisp-set-directory-command "(chdir \"%s\")" ilisp-complete-command "(map (lambda (sym) (list (symbol->string sym))) (apropos-internal \"^%s\"))")) (if (not snd-program) (setq snd-program "snd"))
You can bind this to some key via:
(global-set-key "\C-x\C-l" 'snd)
Now C-x C-l in Emacs starts Snd as a subjob; anything you type in the Emacs *snd* buffer is sent to Snd (very much as if you were running CLM as an Emacs subjob), and Snd's output is appended to the *snd* buffer. This connection still needs some work -- it would be nice to have Snd's command completion available in the *snd* buffer.
You can import shared object files into Snd at any time.
You need to build Snd
with -lguile (that is, load it with the guile shared library, not libguile.a);
if the loader can't find libguile.so.2 (or whatever), add its directory to
your LD_LIBRARY_PATH; for example, if
it's on /usr/local/lib, setenv LD_LIBRARY_PATH /usr/local/lib
.
Next add Guile wrappers to your C code:
/* cscm.c */ #include <math.h> #include <stdio.h> #include <stdlib.h> #include <guile/gh.h> int hiho (int a) { /* this is the function we want to call from Snd */ return(1+a); } SCM hiho_wrapper(SCM a) { /* this tells Guile how to interpret the arguments and return value of hiho */ return(gh_int2scm(hiho(gh_scm2int(a)))); } void init_hiho() { /* this declares hiho within Guile calling the wrapper which calls the C function hiho */ gh_new_procedure1_0("hiho",hiho_wrapper); }
Next compile your code into a shared object (this example is for Linux):
cc -c cscm.c ld -shared -o cscm.so cscm.o -lguile
Now go to Snd's lisp listener and,
(define lib (dynamic-link "/home/bil/cl/cscm.so")) (dynamic-call "init_hiho" lib) (hiho 3)
The function we actually want loaded into Guile here is "hiho". We define a wrapper for it to handle the translation between Guile (Scheme) variable types and C ("hiho_wrapper"), and a procedure to define hiho in Guile ("init_hiho"). Once loaded ("dynamic-link"), we can call the initialization function ("dynamic-call"), and thereafter treat "hiho" as though it had been defined in Guile/Snd to begin with. After both the dynamic-link and dynamic-lib calls, the listener will print "#<unspecified>" or something equally obscure to indicate in its own peculiar way that all went well. M-x (hiho 4) will print 5 in the minibuffer.
To acess and edit sound data from such a module, use the Snd functions make-sample-reader and loop-samples. loop-samples takes a sample reader, a pointer to a (C) float function that takes one float argument (the current sample), the number of times to call that function, and a name for the editing operation for the edit history list. For example, the following module defines a function that scales the data by 2:
#include <guile/gh.h> static float a2(float b) {return(b*2.0);} static SCM get_a2(void) {return(gh_ulong2scm((unsigned long)a2));} void init_hiho() {gh_new_procedure0_0("get-a2",get_a2);}
The "a2" function will be called from Snd as follows; first make the shared object module and load it as above, then
(loop-samples (make-sample-reader 0) (get-a2) 50828 "a2")
There's a way to make a module like this loadable via the (use-modules ...) syntax in Guile, but I haven't delved into it yet.
To call internal Snd functions, you can do something like the following: declare an SCM variable to hold the procedure variable, in the init function set the variable to the value of scm_symbol_value0("function-name"), and in the rest of the code call it via scm_apply. The following is a sketch using the Snd internal "srate" function:
#include <guile/gh.h> static SCM g_srate; static SCM srate_wrapper(SCM a) {return(gh_call1(g_srate,a));} void init_srate(void) { gh_new_procedure1_0("my-srate",srate_wrapper); g_srate = scm_symbol_value0("srate"); }
Alternatively, you can simply use gh_eval_str:
gh_eval_str("(recorder-dialog)"); gh_eval_str("(open-sound \"oboe.snd\")"); srate = gh_scm2int(gh_eval_str("(srate)"));
There is a Scheme to C compiler named Hobbit, but it's not very useful in our context. If you have pure Scheme functions, it may be able to speed them up, but if you want to call Snd/CLM/Sndlib functions, special support is needed, and even with that support my timing tests did not get more than a 50% improvement in speed. Recently, however, Keisuke Nishida started work on a Guile compiler that promises to speed up Snd-Scheme code by at least an order of magnitude. When it becomes available, I'll probably move the vct support stuff back into Scheme, and reduce some of the clutter of functions aimed at fast data access. I think all that's really needed from Snd are the sample-reader functions.
Any external program that knows about sound files can be used to perform editing operations from Snd. You thereby get Snd's display, analysis, header and format conversion, and edit-tree support, and can concentrate on the actual sound effect you're developing. The original impetus for Snd came from CLM, a large lisp-listener based program which normally runs without a graphical user interface, and without any simple way to move around in what Snd calls the edit history. Since interprocess communication proved problematic in this case, the communication path was simplified to consist of little more than shared files, with CLM treated as a batch program. A nice side-effect of this is that any other program can fit the same mold.
For example, say we have a sound processing CLM instrument we like; it takes two sound file names as its arguments, reading the first and writing the second. In Snd we write the current edited state to a temporary file, start CLM, call the instrument passing it the input and output filenames, then pass its output back to Snd. Snd then replaces the current data with the data our instrument wrote, as if it had incorporated that instrument as an editing operation from the beginning.
There are two choices as to what is being edited: either the full sound, or the current active selection of it. And two choices as to how this data should be presented to the external program: either as one, possibly multi-channel file, or as a set of mono files. So we have eight functions (four to write, then four to read), as well as one function to get the current temporary file names (as written by Snd, to be read by the external program):
sound-to-temp (type format) write out sync'd edit state as a temp file sound-to-temps (type format) write out sync'd edit state as temp files selection-to-temp (type format) write out selected data as a temp file selection-to-temps(type format) write out selected data as temp files temp-filenames (data) return vector of temp file names temp-to-selection (data name origin) read selected data from temp file temps-to-selection(data names origin) read selected data from temp files temp-to-sound (data name origin) read sync'd edit state from temp file temps-to-sound (data names origin) read sync'd edit state from temp files
Everything else is handled by scheme code. The type and format arguments default to the currently selected sound's header type and data format, but if your external program can only read a particular kind of file or data, you can specify them here. The external program should not delete either input or output files, and should not overwrite existing output files (Snd handles this bookkeeping).
STK is a synthesis toolkit developed by Perry Cook and Gary Scavone. Like many such programs, it reads a score file and produces an output file. We'll use it here to replace the current sound with a clarinet tone:
(define stk (lambda () (let* ((str "") (data (sound-to-temp)) (fil (open-pipe "syntmono Clarinet -s /tmp/test < scores/hiho.ski" "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) (temp-to-sound data "/tmp/test.snd" "(STK clarinet)") str)))
hiho.ski is:
NoteOn 0.000000 1 60 127.000000 NoteOff 0.126032 1 60 63.500000
The basic sequence is: sound-to-temp writes out the current (possibly edited) state of the selected sound(s) in Snd as a temp file. sound-to-temp returns an opaque object which we will later pass to temp-to-sound to complete the edit. But first, we open a pipe, call STK as a batch job, and read in whatever it prints out (so we can see how the call went). Then we call temp-to-sound passing it the object mentioned earlier, the new filename (the data written by STK that will replace the current data in Snd), and the associated edit-history reference to the operation. In brief:
[sound | selection]-to-[temp | temps] call external program on the data and write new data [temp | temps]-to-[sound | selection]
But this function can't safely be called twice because it always writes "test.snd", and it isn't very useful as an editing operation because it completely ignores the current Snd data. The next steps are to write our data using safe temporary filenames, and read the current data using temp-filenames. We'll also apply this to the current selection, rather than the full file. Since I don't know enough about STK to get it to read an input file, I'll use Sox for the next examples.
Sox is a widely available and well-known program for sound format conversions and various sound effects. In this case, we'll read and write NeXT files, and use Sox's copy "effect".
(define sox (lambda () (let ((data (selection-to-temp))) (if data (let* ((str "") (input-names (temp-filenames data)) (output-name (string-append (tmpnam) ".snd")) (cmd (string-append "sox -t .au \"" (vector-ref input-names 0) "\" -t .au \"" output-name "\" copy")) (fil (open-pipe cmd "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) (temp-to-selection data output-name "(sox copy)") str) (report-in-minibuffer "no current selection")))))
We use the Guile built-in function tmpnam to get an output file name that doesn't collide with any existing file; We then read the incoming filename that Snd wrote (temp-filenames), and pass that to Sox. This is a very complicated no-op, since Sox in this case merely copies its input to its output. We're assuming NeXT/Sun files (the "-t .au" business), and we're blithely ignored the possibility that we might be editing any number of sounds, each with any number of channels. To deal with the latter, we need to notice how many mono files have been passed to us (in the case of sound-to-temps), or our external program needs to be able to handle a file with arbitrarily many channels (sound-to-temp). In the next example, we'll loop through the mono files, processing each in turn. We'll also start packaging up the boilerplate a bit.
(define execute-and-wait (lambda (cmd) (let ((str "") (fil (open-pipe cmd "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) str))) (define loop-through-files (lambda (description make-cmd) (let* ((data (sound-to-temps)) (input-names (temp-filenames data)) (files (vector-length input-names)) (output-names (make-vector files ""))) (do ((i 0 (1+ i))) ((= i files)) (vector-set! output-names i (string-append (tmpnam) ".snd")) (execute-and-wait (make-cmd (vector-ref input-names i) (vector-ref output-names i)))) (temps-to-sound data output-names description)))) (define sox-1 (lambda () (loop-through-files "(sox copy)" (lambda (in out) (string-append "sox -t .au \"" in "\" -t .au \"" out "\" copy")))))
Now our sox function can handle any number of files or channels that might be sync'd together in Snd. In case it's not obvious, the function loop-through-files takes as its second argument a function of two arguments, and calls it on each file as we march through the input file list, passing it the input and output file names as arguments. It (make-cmd) puts together the actual call on sox that we were making earlier. An equivalent using cp is:
(define copyfile (lambda () (loop-through-files "(cp)" (lambda (in out) (string-append "cp " in " " out)))))
But we're still assuming NeXT/Sun format files, and we're throwing away the string we so laboriously created. A more friendly function would display its progress.
Reading, mixing, and writing sound files are no problem in CLM, but
it's unusual to run it as a batch program.
Assume for the moment we have loaded the CLM instruments we want (v.ins and jcrev.ins),
and have saved the image using ACL 5.0 in Linux. The CLM image is
invoked in this case with lisp -I clm.dxl
. ACL provides
a way (-e) to evaluate lisp code from the command line, so
we'll use that along with the exit function to turn CLM
into a batch program. For example, we can reverberate the current data:
(define reverb (lambda (reverb-amount) (loop-through-files (string-append "(reverb " (number->string reverb-amount) ")") (lambda (in out) (string-append "lisp -I clm.dxl " "-e '(progn (restart-clm) " " (with-sound (:play nil :output \"" out "\" :reverb jc-reverb) " " (mix \"" in "\") " " (mix \"" in "\" :output *reverb* :amplitude " (number->string reverb-amount) "))" " (exit))'")))))
This is a call on CLM's with-sound with a reverberator
and two calls on mix, one for the direct signal, the
other for the reverb input. The with-sound form is wrapped up
in a progn that calls restart-clm (to make sure all dynamically
allocated entities are setup properly), the with-sound itself,
then exit to leave lisp (the latter is needed since we're
waiting for EOF in the execute-and-wait function).
The reverb function's argument sets the amount of
reverb, and we save that value in the edit-history descriptor.
Now, in Snd, M-x (reverb .1)
reverbs the current
data and extends the edit-history list with the string "(reverb .1)".
This example also shows how to mix something into the current
data. For example, to add an fm-violin note starting at the
current cursor:
(define fm-violin (lambda (dur frq amp) (let* ((beg (/ (cursor) (srate))) (fmv-call (string-append "(fm-violin " (number->string beg) " " (number->string dur) " " (number->string frq) " " (number->string amp) ")"))) (loop-through-files fmv-call (lambda (in out) (string-append "lisp -I clm.dxl " "-e '(progn (restart-clm) " " (with-sound (:play nil :output \"" out "\") " " (mix \"" in "\") " fmv-call " ) (exit))'"))))))
But if anything goes wrong, the whole process gets hung, since Lisp drops into its error handler, and Snd is waiting for the Lisp job to exit -- we have to go to a shell and kill the Lisp subjob! So let's check for C-g in Snd, and send the subjob output to Guile's "current-output-port" (whatever that is):
(define read-or-run (lambda (fil) (let ((val (peek-char fil))) (or (and val (read-char fil)) (abort?) (read-or-run fil))))) (define execute-and-wait (lambda (cmd) (let ((fil (open-pipe cmd "r"))) (do ((val (read-or-run fil) (read-or-run fil))) ((or (eq? val #t) (eof-object? val)) (eq? val #t)) (write-char val (current-output-port))) (close-pipe fil)))) (define loop-through-files (lambda (description make-cmd) (let* ((data (sound-to-temps)) (input-names (temp-filenames data)) (files (vector-length input-names)) (output-names (make-vector files "")) (stopped #f)) (do ((i 0 (1+ i))) ((or stopped (= i files))) (vector-set! output-names i (string-append (tmpnam) ".snd")) (set! stopped (execute-and-wait (make-cmd (vector-ref input-names i) (vector-ref output-names i))))) (temps-to-sound data output-names description))))
If this is too ugly, we could probably use append-to-minibuffer instead of write-char. In Clisp, use the -x switch without the exit function call. Also, place the expression to be evaluated in double quotes, rather than ACL's single quotes.
To include the entire Snd editor as a widget in some other program, first compile it with -DSND_AS_WIDGET. Then load it into your program, using the procedure snd_as_widget to fire it up. The program saw.c included with Snd is a very brief example.
void snd_as_widget(int argc, char **argv, XtAppContext app, Widget parent, Arg *caller_args, int caller_argn)
starts up the Snd editor in the widget parent, passing the outer Snd form widget the arguments caller_args and caller_argn. The enclosing application context is app. parent needs to be realized at the time of the call, since Snd uses it to set up graphics contexts and so on. argc and argv can be passed to simulate a shell invocation of Snd. Remember that in this case, the first string argument is expected to be the application name, and is ignored by Snd.
In Gtk, the arguments are different, but the basic idea is the same. saw.c has an example.
The files clm.c, clm.h, and clm2scm.c implement CLM (a Common Lisp Music V implementation described in clm.html, available in clm-2.tar.gz at ccrma-ftp) as a Guile-loadable module. They are normally loaded into Snd when it is built. You can see what a generator does, or a group of generators, by running them in the lisp listener, and using the graph and spectrum functions. For example, say we have these declarations in ~/.snd:
(define data-size 1024) (define data (make-vct data-size)) (define run (lambda (fun) (do ((i 0 (1+ i))) ((= i data-size)) (vct-set! data i (fun))) (graph data))) (define runf (lambda (fun) (do ((i 0 (1+ i))) ((= i data-size)) (vct-set! data i (fun))) (graph (snd-spectrum data blackman2-window data-size #t))))
Now we can open the listener, and type:
(define hi (make-oscil)) (run (lambda () (oscil hi))) (define ho (make-oscil)) (runf (lambda () (oscil hi (* .5 (oscil ho)))))
Obviously, any CLM instrument or function can be used in this way to edit sounds, and so on. Say we want an echo effect:
(define echo (lambda (scaler secs) (let ((del (make-delay (round (* secs (srate)))))) (lambda (inval) (+ inval (delay del (* scaler (+ (tap del) inval))))))))
For readers who are new to Scheme, echo is a function of two arguments, scaler and secs. Scaler sets how loud subsequent echos are, and secs sets how far apart they are in seconds. echo uses the secs argument to create a delay line (make-delay) using the current sound's sampling rate to turn the secs parameter into samples. echo then returns a "closure", that is, a function with associated variables (in this case del and scaler); the returned function (the second lambda) takes one argument (inval) and returns the result of passing that value to the delay with scaling. The upshot of all this is that we can use:
(map-chan (echo .5 .75) 0 44100)
to take the current active channel and return 44100 samples of echos, each echo half the amplitude of the previous, and spaced by .75 seconds. map-chan's first argument is a function of one argument, the current sample; when we pass it (echo ...), it evaluates the echo call, which returns the function that actually runs the delay line, producing the echo. The CLM (common lisp) version might be something like:
(definstrument echo (beg dur scaler secs file) (let ((del (make-delay (round (* secs *srate*)))) (inf (open-input file)) (j 0)) (run (loop for i from beg below (+ beg dur) do (let ((inval (ina j inf))) (outa i (+ inval (delay del (* scaler (+ (tap del) inval))))) (incf j)))) (close-input inf))) ;;; (with-sound () (echo 0 60000 .5 1.0 "pistol.snd"))
See clm.html for full details. Optional args are in italics.
all-pass (gen input pm) all-pass filter all-pass? (gen) #t if gen is all-pass filter amplitude-modulate (carrier in1 in2) amplitude modulation array-interp (arr x) interpolated array lookup array->file (filename vct len srate channels) write the contents of vct to the newly created sound file filename, giving the new file channels channels (data assumed to be interleaved in vct), sampling rate srate, and len samples (not frames). asymmetric-fm (gen index fm) asymmetric-fm generator asymmetric-fm? (gen) #t if gen is asymmetric-fm generator buffer->frame (gen frame) buffer generator returning frame buffer->sample (gen) buffer generator returning sample buffer-empty? (gen) #t if buffer has no data buffer-full? (gen) #t if buffer has no room for more data buffer? (gen) #t if gen is buffer generator clear-array (arr) set all elements of arr to 0.0 comb (gen input pm) comb filter comb? (gen) #t if gen is comb filter contrast-enhancement(input (index 1.0)) a kind of phase modulation or companding convolution (sig1 sig2 n) convolve sig1 with sig2 (size n), returning new sig1 convolve (gen input-function) convolve generator convolve? (gen) #t if gen is convolve generator convolve-files (f1 f2 maxamp outf) convolve f1 with f2, normalize to maxamp, write outf db->linear (db) translate dB value to linear degrees->radians (deg) translate degrees to radians delay (gen input pm) delay line delay is a built-in syntactic form (or whatever they call it) in Scheme, but I don't think this is a case where I care! The name %delay is bound to the original meaning of delay in case you need to use it. delay? (gen) #t if gen is delay line dot-product (sig1 sig2) return dot-product of sig1 with sig2 env (gen) envelope generator env-interp (x env (base 1.0)) return value of env at x env? (gen) #t if gen is env (from make-env) mus-fft (rl im n sign) fft of rl and im (sign = -1 for ifft), result in rl file->array (filename chan start len vct) load len samples of filename into vct starting at frame start in channel chan. file->frame (gen loc frame) return frame from file at loc file->frame? (gen) #t if gen is file->frame generator file->sample (gen loc (chan 0)) return sample from file at loc file->sample? (gen) #t if gen is file->sample generator filter (gen input) filter filter? (gen) #t if gen is filter fir-filter (gen input) FIR filter fir-filter? (gen) #t if gen is fir filter formant (gen input) formant generator formant-bank (scls gens invals) formant? (gen) #t if gen is formant generator frame* (fr1 fr2 outfr) element-wise multiply frame+ (fr1 fr2 outfr) element-wise add frame->buffer (buf frame) add frame to buffer frame->file (gen loc frame) write (add) frame to file at loc frame->file? (gen) #t if gen is frame->file generator frame->frame (mixer frame outfr) pass frame through mixer frame->list (frame) return list of frame contents frame-ref (frame chan) return frame[chan] frame->sample (frmix frame) pass frame through frame or mixer to produce sample frame-set! (frame chan val) frame[chan]=val frame? (gen) #t if gen is frame object granulate (gen input-function) granular synthesis generator granulate? (gen) #t if gen is granulate generator hz->radians (freq) translate freq to radians/sample iir-filter (gen input) IIR filter iir-filter? (gen) #t if gen is iir-filter in-any (loc chan stream) return sample in stream at loc and chan in-hz (freq) translate freq to radians/sample ina (loc stream) return sample in stream at loc, chan 0 inb (loc stream) return sample in stream at loc, chan 1 linear->db (val) translate linear val to dB locsig (gen loc input) place input in output channels at loc locsig-ref (gen chan) locsig-scaler[chan] locsig-reverb-ref (gen chan) locsig-reverb-scaler[chan] locsig-set! (gen chan val) locsig-scaler[chan] = val locsig-reverb-set! (gen chan val) locsig-reverb-scaler[chan] = val locsig? (gen) #t if gen is locsig generator ;; all the make function arguments are optional-key args make-all-pass (feedback feedforward size initial-contents initial-element max-size) make-asymmetric-fm (frequency initial-phase r ratio) make-buffer (size fill-time) make-comb (scaler size initial-contents initial-element max-size) make-convolve (input filter fft-size filter-size) make-delay (size initial-contents initial-element max-size) make-env (envelope scaler duration offset base end start) make-fft-window (type size beta) make-file->frame (name) make-file->sample (name) make-filter (order xcoeffs ycoeffs) make-fir-filter (order xcoeffs) make-formant (radius frequency gain) make-frame (chans &rest vals) make-frame->file (name chans format type) make-granulate (input expansion length scaler hop ramp jitter max-size) make-iir-filter (order ycoeffs) make-locsig (degree distance reverb output revout channels) make-mixer (chans &rest vals) make-notch (scaler size initial-contents initial-element max-size) make-one-pole (a0 b1) make-one-zero (a0 a1) make-oscil (frequency initial-phase) make-phase-vocoder (fftsize overlap interp pitch analyze edit synthesize) make-ppolar (radius frequency) make-pulse-train (frequency amplitude initial-phase) make-rand (frequency amplitude) make-rand-interp (frequency amplitude) make-readin (file channel start) make-sample->file (name chans format type) make-sawtooth-wave (frequency amplitude initial-phase) make-sine-summation (frequency initial-phase n a ratio) make-square-wave (frequency amplitude initial-phase) make-src (input srate width) make-sum-of-cosines (frequency initial-phase cosines) make-table-lookup (frequency initial-phase wave) make-triangle-wave (frequency amplitude initial-phase) make-two-pole (a0 b1 b2) make-two-zero (a0 a1 a2) make-wave-train (frequency initial-phase wave) make-waveshape (frequency partials) make-zpolar (radius frequency) mixer* (mix1 mix2 outmx) matrix multiply of mix1 and mix2 mixer-ref (mix in out) mix-scaler[in,out] mixer-set! (mix in out val) mix-scaler[in,out] = val mixer? (gen) #t if gen is mixer object multiply-arrays (arr1 arr2) arr1[i] *= arr2[i] mus-a0 (gen) a0 field (simple filters) mus-a1 (gen) a1 field (simple filters) mus-a2 (gen) a2 field (simple filters) mus-array-print-length () how many array elements to print in mus_describe mus-b1 (gen) b1 field (simple filters) mus-b2 (gen) b2 field (simple filters) mus-channel (gen) channel of gen mus-channels (gen) channels of gen mus-cosines (gen) cosines of sum-of-cosines gen mus-data (gen) data array of gen mus-feedback (gen) feedback term of gen (simple filters) mus-feedforward (gen) feedforward term of gen (all-pass) mus-formant-radius (gen) formant radius mus-frequency (gen) frequency of gen (Hz) mus-hop (gen) hop amount of gen (granulate) mus-increment (gen) increment of gen (src, readin, granulate) mus-input? (gen) #t if gen is input source mus-length (gen) length of gen mus-location (gen) location (read point) of gen mus-mix (outfile infile (outloc 0) frames (inloc 0) mixer envs) mix infile into outfile starting at outloc in outfile and inloc in infile mixing frames frames of infile. frames defaults to the length of infile. If mixer, use it to scale the various channels; if envs (an array of envelope generators), use it in conjunction with mixer to scale/envelope all the various ins and outs. mus-order (gen) order of gen (filters) mus-output? (gen) #t if gen is output generator mus-phase (gen) phase of gen (radians) mus-ramp (gen) ramp time of gen (granulate) mus-random (val) random numbers bewteen -val and val mus-scaler (gen) scaler of gen mus-set-rand-seed (val) set random number generator seed to val mus-set-srate (val) set sampling rate to val -- (set! (mus-srate) val) is the same. mus-srate () current sampling rate mus-xcoeffs (gen) feedforward (FIR) coeffs of filter mus-ycoeffs (gen) feedback (IIR) coeefs of filter notch (gen input pm) notch filter notch? (gen) #t if gen is notch filter one-pole (gen input) one-pole filter one-pole? (gen) #t if gen is one-pole filter one-zero (gen input) one-zero filter one-zero? (gen) #t if gen is one-zero filter oscil (gen fm pm) sine wave generator oscil-bank (scls gens invals) bank of oscils oscil? (gen) #t if gen is oscil generator out-any (loc samp chan stream) write (add) samp to stream at loc in channel chan outa (loc samp stream) write (add) samp to stream at loc in chan 0 outb (loc samp stream) write (add) samp to stream at loc in chan 1 outc (loc samp stream) write (add) samp to stream at loc in chan 2 outd (loc samp stream) write (add) samp to stream at loc in chan 3 partials->polynomial(partials kind) create waveshaping polynomial from partials partials->wave (synth-data table norm) load table from synth-data partials->waveshape (partials norm size) create waveshaping table from partials phase-partials->wave(synth-data table norm) load table from synth-data phase-vocoder (pv input) phase vocoder generator phase-vocoder? (pv) #t if pv is phase vocoder generator polynomial (coeffs x) evaluate polynomial at x pulse-train (gen fm) pulse-train generator pulse-train? (gen) #t if gen is pulse-train generator radians->degrees (rads) convert radians to degrees radians->hz (rads) convert radians/sample to Hz rand (gen fm) random number generator rand-interp (gen fm) interpolating random number generator rand-interp? (gen) #t if gen is interpolating random number generator rand? (gen) #t if gen is random number generator readin (gen) read one value from associated input stream readin? (gen) #t if gen is readin generator rectangular->polar (rl im) translate from rectangular to polar coordinates restart-env (env) return to start of env ring-modulate (sig1 sig2) sig1 * sig2 (element-wise) sample->buffer (buf samp) store samp in buffer sample->file (gen loc chan val) store val in file at loc in channel chan sample->file? (gen) #t if gen is sample->file generator sample->frame (frmix samp outfr) convert samp to frame sawtooth-wave (gen fm) sawtooth-wave generator sawtooth-wave? (gen) #t if gen is sawtooth-wave generator sine-summation (gen fm) sine-summation generator sine-summation? (gen) #t if gen is sine-summation generator spectrum (rl im win type) produce spectrum of data in rl (return rl) square-wave (gen fm) square-wave generator square-wave? (gen) #t if gen is square-wave generator src (gen fm input-function) sample rate converter src? (gen) #t if gen is sample-rate converter sum-of-cosines (gen fm) sum-of-cosines (pulse-train) generator sum-of-cosines? (gen) #t if gen is sum-of-cosines generator sum-of-sines (amps phases) additive synthesis table-lookup (gen fm) table-lookup generator table-lookup? (gen) #t if gen is table-lookup generator tap (gen pm) delay line tap triangle-wave (gen fm) triangle-wave generator triangle-wave? (gen) #t if gen is triangle-wave generator two-pole (gen input) two-pole filter two-pole? (gen) #t if gen is two-pole filter two-zero (gen input) two-zero filter two-zero? (gen) #t if gen is two-zero filter wave-train (gen fm) wave-train generator wave-train? (gen) #t if gen is wave-train generator waveshape (gen index fm) waveshaping generator waveshape? (gen) #t if gen is waveshape generator
formant-bank and oscil-bank are optimizations for situations like the phase vocoder or cross synthesis (see examp.scm). It is assumed that you have a vector of generators, all summing their outputs into a single float. The amplitude scalers (the first argument to the bank function) can be a float, a vector of floats, a vct of floats, or a function that returns a float each time it is called; similarly for the inputs (the third argument); the bank of generators (the second argument) is assumed to be a vector full of generators.
(formant-bank amps gens inputs)
is essentially the same as (but 30 times faster than)
(do ((sum 0.0) (i 0 (1+ i))) ((= i (vct-length gens)) sum) (set! sum (+ sum (* (vector-ref amps i) (formant (vector-ref gens i) (vector-ref inputs i))))))
in the all-vector case. Here are a few more examples, taken from examp.scm.
(define comb-filter (lambda (scaler size) (let ((cmb (make-comb scaler size))) (lambda (x) (comb cmb x))))) ; (map-chan (comb-filter .8 32)) ;;; by using filters at harmonically related sizes, we can get chords: (define comb-chord (lambda (scaler size amp) (let ((c1 (make-comb scaler size)) (c2 (make-comb scaler (* size .75))) (c3 (make-comb scaler (* size 1.2)))) (lambda (x) (* amp (+ (comb c1 x) (comb c2 x) (comb c3 x))))))) ; (map-chan (comb-chord .95 60 .3)) ;;; or change the comb length via an envelope: (define max-envelope (lambda (e mx) (if (null? e) mx (max-envelope (cddr e) (max mx (abs (cadr e))))))) (define zcomb (lambda (scaler size pm) (let ((cmb (make-comb scaler size :max-size (+ size 1 (max-envelope pm 0)))) (penv (make-env :envelope pm :end (frames)))) (lambda (x) (comb cmb x (env penv)))))) ; (map-chan (zcomb .8 32 '(0 0 1 10))) ;;; to impose several formants, just add them in parallel: (define formants (lambda (r1 f1 r2 f2 r3 f3) (let ((fr1 (make-formant r1 f1)) (fr2 (make-formant r2 f2)) (fr3 (make-formant r3 f3))) (lambda (x) (+ (formant fr1 x) (formant fr2 x) (formant fr3 x)))))) ; (map-chan (formants .01 900 .02 1800 .01 2700)) ;;; to get a moving formant: (define moving-formant (lambda (radius move) (let ((frm (make-formant radius (cadr move))) (menv (make-env :envelope move :end (frames)))) (lambda (x) (let ((val (formant frm x))) (set! (mus-frequency frm) (env menv)) val))))) ; (map-chan (moving-formant .01 '(0 1200 1 2400))) ;;; various "Forbidden Planet" sound effects: (define sp (lambda (sr osamp osfrq) (let* ((os (make-oscil osfrq)) (sr (make-src :srate sr)) (len (frames)) (sf (make-sample-reader)) (out-data (make-vct len))) (vct-map! out-data (lambda () (src sr (* osamp (oscil os)) (lambda (dir) (if (> dir 0) (next-sample sf) (previous-sample sf)))))) (free-sample-reader sf) (vct->samples 0 len out-data)))) ; (fp 1.0 .3 20) ;;; -------- shift pitch keeping duration constant ;;; ;;; both src and granulate take a function argument to get input whenever it is needed. ;;; in this case, src calls granulate which reads the currently selected file. (define expsrc (lambda (rate) (let* ((gr (make-granulate :expansion rate)) (sr (make-src :srate rate)) (vsize 1024) (vbeg 0) (v (samples->vct 0 vsize)) (inctr 0)) (lambda (inval) (src sr 0.0 (lambda (dir) (granulate gr (lambda (dir) (let ((val (vct-ref v inctr))) (set! inctr (+ inctr dir)) (if (>= inctr vsize) (begin (set! vbeg (+ vbeg inctr)) (set! inctr 0) (samples->vct vbeg vsize 0 0 v))) val)))))))))
Geez, I haven't had this much fun in a long time! Check out examp.scm and snd-test.scm for more.
It is possible to add your own user-interface elements. As a very simple example, let's make a dialog window with a slider:
#include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <guile/gh.h> #include <Xm/XmAll.h> static Widget scale_dialog = NULL; /* this will hold our slider */ static float current_scaler = 1.0; static void Help_Scale_Callback(Widget w,XtPointer clientData,XtPointer callData) { fprintf(stderr,"move the slider to affect the volume"); } static void Dismiss_Scale_Callback(Widget w,XtPointer clientData,XtPointer callData) { XtUnmanageChild(scale_dialog); } static void Scale_Callback(Widget w,XtPointer clientData,XtPointer callData) { XmScaleCallbackStruct *cb = (XmScaleCallbackStruct *)callData; current_scaler = (float)(cb->value/100.0); } static void create_scale_dialog(Widget parent) { Arg args[32]; int n,i; XmString xhelp,xdismiss,titlestr; Widget mainform,scale; if (!scale_dialog) { xdismiss = XmStringCreate("Dismiss",XmFONTLIST_DEFAULT_TAG); xhelp = XmStringCreate("Help",XmFONTLIST_DEFAULT_TAG); titlestr = XmStringCreate("Scaling",XmFONTLIST_DEFAULT_TAG); n=0; XtSetArg(args[n],XmNcancelLabelString,xdismiss); n++; XtSetArg(args[n],XmNhelpLabelString,xhelp); n++; XtSetArg(args[n],XmNautoUnmanage,FALSE); n++; XtSetArg(args[n],XmNdialogTitle,titlestr); n++; XtSetArg(args[n],XmNresizePolicy,XmRESIZE_GROW); n++; XtSetArg(args[n],XmNnoResize,FALSE); n++; XtSetArg(args[n],XmNtransient,FALSE); n++; scale_dialog = XmCreateTemplateDialog(parent,"Scaling",args,n); XtAddCallback(scale_dialog,XmNcancelCallback,Dismiss_Scale_Callback,NULL); XtAddCallback(scale_dialog,XmNhelpCallback,Help_Scale_Callback,NULL); XmStringFree(xhelp); XmStringFree(xdismiss); XmStringFree(titlestr); n=0; XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++; XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++; XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++; XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++; XtSetArg(args[n],XmNbottomWidget,XmMessageBoxGetChild(scale_dialog,XmDIALOG_SEPARATOR)); n++; mainform = XtCreateManagedWidget("formd",xmFormWidgetClass,scale_dialog,args,n); n=0; XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++; XtSetArg(args[n],XmNshowValue,TRUE); n++; XtSetArg(args[n],XmNvalue,100); n++; XtSetArg(args[n],XmNmaximum,500); n++; XtSetArg(args[n],XmNdecimalPoints,2); n++; scale = XtCreateManagedWidget("",xmScaleWidgetClass,mainform,args,n); XtAddCallback(scale,XmNvalueChangedCallback,Scale_Callback,NULL); XtAddCallback(scale,XmNdragCallback,Scale_Callback,NULL); } XtManageChild(scale_dialog); } static SCM hiho_scaler(void) { return(gh_double2scm(current_scaler)); } static SCM g_create_scale_dialog(SCM parent) { /* assumes we're called in listener as (create-scale-dialog (snd-main-shell)) */ /* snd-main-shell returns Snd's topmost widget */ create_scale_dialog((Widget)gh_scm2ulong(parent)); return(SCM_BOOL_F); } void init_hiho() { gh_new_procedure1_0("create-scale-dialog",g_create_scale_dialog); gh_new_procedure0_0("hiho-scaler",hiho_scaler); } /* call this hiho.c: * gcc -c hiho.c -o hiho.o * ld -shared hiho.o -o hiho.so -L/usr/X11R6/lib -lXm -lXt -lXext -lX11 -L/usr/local/lib -lguile -ldl * in Snd: * (define hiho (dynamic-link "/home/bil/cl/hiho.so")) * (dynamic-call "init_hiho" hiho) * (create-scale-dialog (snd-main-shell)) * * if the dynamic-link says "file not found", this normally means it can't find one of the needed * libraries; this in turn normally means your /etc/ld.so.conf file needs to be updated to include * whatever directory is missing, then run (as root) /sbin/ldconfig. */
I've heard rumors of a guile-motif package similar (I presume) to the guile-gtk package (see below). If it surfaces, I'll add some immortal prose here.
In the Gtk+ version of Snd, the .Xdefaults file is replaced by .sndrc, but I have no idea how it's supposed to work. The easiest way to add your own user interface widgets to Snd is through guile-gtk. This package ties gtk into guile which is already tied into Snd. It's available from http://www.ping.de/sites/zagadka/guile-gtk/ or possibly from the various guile sites. As a quick example, take the file simple.scm included in the guile-gtk examples directory; in its original form, it assumes it is the top-level program, but it is very easy to modify it to be a dialog in Snd (my changes are in red):
(use-modules (gtk gtk)) (define hiho (lambda () (let* ((window (gtk-widget-new 'GtkWindow :type 'dialog :title "hello world" :allow_grow #f :allow_shrink #f :GtkContainer::border_width 10)) (label (gtk-widget-new 'GtkLabel :label "hello world" :visible #t)) (button (gtk-widget-new 'GtkButton :child label :parent window :visible #t))) (gtk-signal-connect button "clicked" (lambda () (display (gtk-object-get label :label)) (newline) (gtk-widget-set label :label "yo!"))) (gtk-widget-show window)))) ;deleted the line (gtk-standalone-main window)
Now rather than making a top-level window, this little example creates
a dialog in Snd; say we have saved it as simple.scm. We can load it into
Snd at any time using the "load" function, or in the Snd invocation using
the "-l" switch: snd oboe.snd -l simple.scm
.
Now call the function (hiho)
and the dialog appears! A more
useful dialog might play the current sound with a scaler to change the
output amplitude (we're placing a "play" menu option in the options menu;
when that is activated, the dialog fires up):
(use-modules (gtk gtk) (gtk gdk)) (define amp 1.0) (define play-dialog-menu (gtk-menu-item-new-with-label "play")) (gtk-menu-append (sg-options-menu-widget) play-dialog-menu) (gtk-widget-show play-dialog-menu) (gtk-signal-connect play-dialog-menu "activate" (lambda () (let* ((window (gtk-dialog-new)) (adj (gtk-adjustment-new 1.0 0.0 1.01 .01 .01 .01)) (scale (gtk-hscale-new adj)) (button (gtk-button-new-with-label "play"))) (gtk-box-pack-start (gtk-dialog-action-area window) scale #t #t 2) (gtk-range-set-update-policy scale 'continuous) (gtk-scale-set-digits scale 2) (gtk-scale-set-draw-value scale #t) (gtk-widget-show scale) (gtk-signal-connect adj "value_changed" (lambda () (set! amp (gtk-adjustment-value adj)))) (gtk-box-pack-start (gtk-dialog-action-area window) button #f #f 2) (gtk-signal-connect button "clicked" (lambda () (let* ((size 256) (data (make-sound-data 1 size)) (bytes (* size 2)) (len (frames)) (beg 0) (audio-fd (mus-audio-open-output mus-audio-default 22050 1 mus-lshort bytes))) (if (not (= audio-fd -1)) (do () ((or (abort?) (>= beg len)) (mus-audio-close audio-fd)) (vct->sound-data (vct-scale! (samples->vct beg size) amp) data 0) (mus-audio-write audio-fd data size) (set! beg (+ beg size))))))) (gtk-widget-show button) (gtk-widget-show window))))
If you want access to Snd's own widgets, you need to load libguilegtk and include the HAVE_GUILE_GTK flag -- presumably this could also be done at run time somehow. If you built Snd using the configure script, guile-gtk will be loaded if it can be found. Once loaded, functions of the following form give access to Snd's internal widgets, graphics contexts, and so on:
sg-channel-graph-widget (snd chn) the drawing area widget associated with snd's channel chn sg-selected-cursor-gc () the GdkGC corresponding to the selected-cursor sg-file-open-menu-widget () the File:Open menu widget sg-listener-font () the listener font
There are a bazillion such widgets, all with self-evident names (it sez here). The actual names can be found in the snd-g*.c files under the HAVE_GUILE_GTK switch. As a simple example, here's how to draw a big rectangle in the time domain graph (assuming a sound is open, so the graph widget exists):
(use-modules (gtk gtk) (gtk gdk)) (let* ((win (gtk-widget-window (sg-channel-graph-widget))) (gc (gdk-gc-new win))) (gdk-draw-rectangle win gc #t 0 0 100 100))
You can be notified of an expose event on that graph via:
(gtk-signal-connect (sg-channel-graph-widget) "expose_event" (lambda (ev) (display "exposed!") (newline)))
But the graph is updated on all kinds of occasions, so rather than try to guess all the signals involved, it's much easier to tie our graphics code into the after-graph-hook:
(define show-info (lambda (snd chn) (let ((ls (left-sample snd chn)) (rs (right-sample snd chn))) (if (and (< ls 1000) (> rs 1000)) (let* ((win (gtk-widget-window (sg-channel-graph-widget snd chn))) (gc (gdk-gc-new win)) (font (sg-listener-font)) (pos (sg-sample->x 1000))) (gdk-draw-rectangle win gc #f pos 10 50 20) (gdk-draw-string win font gc (+ pos 2) 24 "hiho")))))) (add-hook! after-graph-hook show-info)
Now the text "hiho" in a rectangle is display above sample 1000 whenever it happens to fall within the current graph display. The function sg-sample->x returns the widget-relative x position of a given sample. To get rid of a menu item that is of no interest:
(gtk-widget-hide (sg-view-consoles-menu-widget))
Or to add a new top level menu named "my menu" with an option named "one":
(define new-menu (gtk-menu-item-new-with-label "my menu")) (gtk-menu-bar-append (sg-menu-bar-widget) new-menu) (gtk-widget-show new-menu) (define cascade-menu (gtk-menu-new)) (gtk-menu-item-set-submenu new-menu cascade-menu) (define one-menu (gtk-menu-item-new-with-label "one")) (gtk-menu-append cascade-menu one-menu) (gtk-widget-show one-menu)
See snd-gtk.scm for more examples.
gmeteor is a Guile-based filter design package written by Matteo Frigo, based on the Meteor system of Steiglitz, Parks, and Kaiser. It is freely available here. Once installed, it can be loaded into Snd and used to define filters very easily: (here I'm typing in Snd's listener and editing the numbers for legibility; the file gm.scm is taken nearly verbatim from the gmeteor script):
>(load "gm.scm") #<unspecified> >(load "../test/gmeteor-0.9/examples/example-1.scm") #<unspecified> >*coefficients* #(0.0197 -0.0406 -0.0739 0.1340 0.4479 0.4479 0.13403 -0.0739 -0.0406 0.0197) >(filter-sound (vector->vct *coefficients*) (vector-length *coefficients*))
There is one small problem: both Snd (CLM) and gmeteor define a function named make-filter. Someday I'll learn enough about the Guile module system to know how to keep gmeteor's make-filter from clobbering CLM's. (There may be others as well -- this is the one I happened to notice).
Richard Furse has provided a module to support LADSPA plugins in Snd. To get it loaded, either use the configure switch --with-ladspa, or include the compile flag HAVE_LADSPA. Here is documentation from Richard Furse:
Supporting functions are: (init-ladspa) Performs a search of LADSPA_PATH for plugins, doesn't need to be called as LADSPA automatically initialises on first use however can be used to reinitialise if new plugins have arrived. (list-ladspa) Returns a list of lists where each inner list contains a string to identify the plugin library and a string to identify the plugin type within the library. (analyse-ladspa plugin-library plugin-type) Returns a list of assorted data about a particular plugin including a list of port descriptions. plugin-library and plugin-type are as provided by list-ladspa. The main function is: (apply-ladspa reader (plugin-library plugin-type [param1 [param2 ...]]) samples origin) Applies a LADSPA plugin in a way very similar to loop-samples - essentially a plugin identifier and parameter set takes the place of func. An example call to apply the low-pass-filter in the CMT plugin library is (apply-ladspa (make-sample-reader 0) (list "cmt" "lpf" 1000) 10000 "origin").
It is possible to send Snd arbitrary scheme code from any other program; the program sndctrl.c is a simple example. Snd has two X window properties: "SND_VERSION" and "SND_COMMAND"; the former is the Snd version (a date), and the latter is the communication path for other programs. Any time such a program changes the SND_COMMAND property, Snd notices and evaluates the new value (as a string, as if typed in the Snd lisp listener). To get a response from Snd, use the function change-property(consat,name,command) where consat is the property name Snd should search for, name is the property to change, and command is the string that replaces the current property value.
Snd can be used in conjunction with OpenGL, but due to the way GL uses X, it's not built into the Snd image. The files glfft.c and glfft.scm show one way to get GL graphics of Snd data. glfft.c is a program that sets up a Motif/Mesa GL drawing area widget, then sits in a loop watching for Snd-generated spectrogram data. Whenever any appears, it displays it using (exceedingly primitive) GL commands. glfft.scm is the Snd side of the process; it puts a function on the after-fft hook to write the spectrogram data to the shared file. To use this stuff, build glfft, start Snd, display spectrograms, load glfft.scm, and (start-gl).
abort fit-data-on-open mus-create select-sound abort? fixup-helpers mus-data-format-name selected-channel activate-listener formant mus-error-hook selected-data-color add-clm-method formant? mus-feedback selected-graph-color add-mark forward-graph mus-feedforward selected-mix add-mark forward-mark mus-formant-radius selected-sound add-player forward-mix mus-frequency selection-beg add-region forward-sample mus-header-type-name selection-color add-sound-file-extension fourier-transform mus-hop selection-length add-to-main-menu frame* mus-increment selection-member add-to-menu frame+ mus-input? selection-to-temp add-transform frame->buffer mus-ircam selection-to-temps after-graph-hook frame->file mus-length selection? after-open-hook frame->file? mus-location send-and-receive-snd all-pass frame->frame mus-next send-snd all-pass? frame->list mus-open-read set-contrast-func amp frame->sample mus-open-write set-expand-funcs amplitude-modulate frame-ref mus-order set-menu-sensitive append-to-minibuffer frame-set! mus-output? set-oss-buffers array->file frame? mus-phase set-reverb-funcs array-interp frames mus-ramp set-samples as-one-edit free-mix-sample-reader mus-random set-sound-loop-info ask-before-overwrite free-sample-reader mus-reopen-write short-file-name asymmetric-fm free-track-sample-reader mus-riff show-axes asymmetric-fm? gmeteor mus-scaler show-fft-peaks audio-output-device granulate mus-set-rand-seed show-indices audio-state-file granulate? mus-set-raw-header-defaults show-listener auto-resize graph mus-sound-chans show-marks auto-update graph->ps mus-sound-comment show-mix-consoles autocorrelate graph-cursor mus-sound-data-format show-mix-waveforms axis-label-font graph-hook mus-sound-data-location show-selection-transform axis-numbers-font graph-lines mus-sound-duration show-usage-stats backward-graph graph-style mus-sound-frames show-y-zero backward-mark graphing mus-sound-header-type showing-controls backward-mix graphs-horizontal mus-sound-length sinc-width backward-sample header-type mus-sound-loop-info sine-summation basic-color headers mus-sound-samples sine-summation? be-helpful help-dialog mus-sound-srate smooth bind-key help-text-font mus-sun smooth-selection bold-button-font hide-listener mus-xcoeffs snd's objects bomb highlight-color mus-ycoeffs snd-apropos buffer->frame hobbit name-click-hook snd-cleanup buffer->sample html-dir new-sound snd-edit buffer-empty? hz->radians next-mix-sample snd-edit-sound buffer-full? id-region next-sample snd-envelope buffer? IIR-filter next-track-sample snd-error button-font IIR-filter? normalize-envelope snd-error-hook call-apply in normalize-fft snd-help centered-random in-any normalize-on-open snd-memo change-menu-label ina normalize-view snd-print channel-style inb notch snd-region channels initial-x0 notch? snd-sound channels-separate initial-x1 notehook snd-spectrum chans initial-y0 one-pole snd-tempnam clear-array initial-y1 one-pole? snd-version clear-audio-inputs *input* one-zero snd-warning *clm-array-print-length* ins-var one-zero? snd-warning-hook *clm-binary-directory* insert-region open-alternate-sound sound-chans *clm-channels* insert-sample open-controls sound-data-format *clm-clipped* insert-samples open-hook sound-data-location *clm-data-format* insert-sound open-input sound-datum-size *clm-date* instrument-let open-input* sound-duration clm-envelope just-sounds *open-input-pathname* sound-files-in-directory *clm-file-buffer-size* just-sounds-hook *open-input-truename* sound-format-name *clm-file-name* key *open-input-verbose* sound-frames *clm-header-type* key-press-hook open-output sound-header-type clm-help left-sample open-raw-sound sound-length *clm-help-browser* line-size open-sound sound-let *clm-init* linear->db open-sound-file sound-loop-info clm-load linux-help optional-key sound-max-amp *clm-news* list->vct orientation-dialog sound-samples *clm-notehook* listener-color oscil sound-set-loop-info *clm-play* listener-font oscil-bank sound-srate *clm-player* listener-prompt oscil? sound-to-temp clm-print load-colormap out-any sound-to-temps clm-random locsig outa sound-type-name *clm-safety* locsig-ref outb sound? *clm-search-list* locsig-reverb-ref outc soundfont-info *clm-source-directory* locsig-reverb-set! outd sounds *clm-srate* locsig-set! *output* spectro-cutoff *clm-table-size* locsig? output-comment-hook spectro-hop *clm-verbose* loop-samples output-name-hook spectro-start *clm-version* make-all-pass override-data-format spectro-x-angle close-controls make-asymmetric-fm override-data-location spectro-x-scale close-hook make-buffer override-data-size spectro-y-angle close-input make-color partials->polynomial spectro-y-scale close-output make-comb partials->wave spectro-z-angle close-sound make-controller partials->waveshape spectro-z-scale close-sound-file make-convolve peaks spectrum color->list make-delay phase-partials->wave speed color-cutoff make-empty-frame phase-vocoder speed-style color-dialog make-empty-mixer phase-vocoder? speed-tones color-inverted make-env play square-wave color-scale make-fcontrol play-and-wait square-wave? color? make-fft-graph play-region squelch-update colormap make-fft-window play-selection srate comb make-file->frame play-track src comb? make-file->sample polynomial src-selection comment make-filter position-color src-sound contrast make-fir-filter prefix-arg src? contrast-amp make-formant preload-directory start-hook contrast-enhancement make-frame preload-file start-playing contrasting make-frame->file previous-files-sort start-playing-hook control make-granulate previous-sample start-progress-report control-allocate make-graph print-length start-snd control-free make-identity-mixer progress-report stop-dac convolution make-iir-filter prompt-in-minibuffer stop-helper convolve make-locsig protect-region stop-helpers convolve-arrays make-mix-sample-reader pulse-train stop-player convolve-files make-mixer pulse-train? stop-playing convolve-selection-with make-notch pushed-button-color stop-playing-channel-hook convolve-with make-one-pole radians->degrees stop-playing-hook convolve? make-one-zero radians->hz stop-playing-region-hook corruption-time make-oscil rand sum-of-cosines count-matches make-phase-vocoder rand-interp sum-of-cosines? cursor make-player rand-interp? sum-of-sines cursor-follows-play make-ppolar rand? swap-channels cursor-in-view make-pulse-train raw-chans syncd-marks cursor-style make-rand raw-format syncing cut make-rand-interp raw-srate table-lookup dac make-readin rc table-lookup? dac-folding make-region read-only tap dac-size make-region-sample-reader readin tell-helpers data-clipped make-roomsig readin? temp-dir data-format make-sample->file rec-any temp-filenames data-location make-sample-reader receive-snd temp-to-selection db->linear make-sawtooth-wave recorder-autoload temp-to-sound def-clm-fun make-sine-summation recorder-buffer-size temps-to-selection def-clm-struct make-square-wave recorder-file temps-to-sound default-output-chans make-sum-of-cosines recorder-gain text-focus-color default-output-format make-table-lookup recorder-in-amp times->samples default-output-srate make-track-sample-reader recorder-in-format tiny-font default-output-type make-triangle-wave recorder-max-duration to-snd definstrument make-two-pole recorder-out-amp track-sample-reader? defpinstrument make-two-zero recorder-out-chans transform-dialog defvar make-vct recorder-out-format transform-samples degrees->radians make-wave-train recorder-srate transform-samples->vct delay make-waveshape recorder-trigger transform-size delay? make-zpolar rectangular->polar transform-type delete-mark map-across-all-chans redo transforms delete-marks map-across-chans region-chans trap-segfault delete-region map-across-envelopes region-dialog triangle-wave delete-sample map-across-sound-chans region-id triangle-wave? delete-samples map-all-chans region-length two-pi describe-instrument map-chan region-maxamp two-pole dismiss-all-dialogs map-chans region-sample two-pole? dlocsig map-sound-chans region-samples two-zero dot-product mark->sound region-samples->vct two-zero? dot-size mark-click-hook region-srate unbind-key during-open-hook mark-color region? undo edit-fragment mark-name regions undo-hook edit-header-dialog mark-sync remote-read uniting edit-hook mark-sync-max remote-write update-fft edit-position mark? remove-from-menu update-graph edits marks reopen-output update-sound env max-fft-peaks report-in-minibuffer use-raw-defaults env-interp max-regions restart-clm use-sinc-interp env-selection max-sounds restart-env vct->list env-sound maxamp restore-control-panel vct->samples env-sound meld-envelopes *reverb* vct->sound-file env? memo-sound reverb-decay vct-add! enved-base min-dB reverb-feedback vct-copy enved-clipping mix reverb-length vct-do! enved-dBing mix reverb-lowpass vct-fill! enved-dialog mix-amp reverb-scale vct-length enved-exping mix-amp-changed-hook reverbing vct-map! enved-power mix-amp-env reverse-selection vct-move! enved-target mix-anchor reverse-sound vct-multiply! enved-waveform-color mix-chans revert-sound vct-offset! enved-waving mix-color right-sample vct-peak envelope* mix-console-amp-scaler ring-modulate vct-ref envelope+ mix-console-speed-scaler run* vct-set! envelope->coeffs mix-console-state safety vct-subtract! envelope-concatenate mix-console-state-changed-hook sample vct? envelope-exp mix-console-y sample->buffer vcts-do! envelope-interp mix-focus-color sample->file vcts-map! envelope-repeat mix-length sample->file? vector->vct envelope-reverse mix-locked sample->frame verbose-cursor envelope-simplify mix-name sample-reader-at-end? view-sound eps-file mix-position sample-reader? volume exit mix-position-changed-hook samples vu-font exit-hook mix-region samples->sound-data vu-font-size expand mix-sample-reader? samples->vct vu-size expand-hop mix-sound save-control-panel wave-train expand-length mix-sound-channel save-dir wave-train? expand-ramp mix-speed save-edit-history wavelet-type expanding mix-speed-changed-hook save-envelopes waveshape fcontrol mix-track save-hook waveshape? fft mix-vct save-macros waving fft-beta mix-waveform-color save-marks wavo fft-graph mix-waveform-height save-options wavo-hop fft-log-frequency mix? save-region wavo-trace fft-log-magnitude mixer* save-selection window-envelope fft-size mixer-ref save-signal window-height fft-style mixer-set! save-sound window-width fft-window mixer? save-sound-as window-x ffting mixes save-state window-y file->array mouse-drag-hook save-state-file with-current-sound file->frame mouse-press-hook save-state-on-exit with-dac file->frame? mouse-release-hook sawtooth-wave with-mix file->sample movies sawtooth-wave? with-mix-consoles file->sample? multichannel-mix-hook scale-by with-offset file-dialog multiply-arrays scale-envelope with-psound file-name mus-a0 scale-selection-by with-sound filter mus-a1 scale-selection-to x-axis-style filter-dBing mus-a2 scale-to x-bounds filter-env mus-aifc scaled-by x-position-slider filter-env-order mus-aiff scaled-to x-zoom-slider filter-order mus-audio-describe scan-across-all-chans y-bounds filter-selection mus-audio-error scan-across-chans y-position-slider filter-sound mus-audio-error-name scan-across-sound-chans y-zoom-slider filter-waveform-color mus-audio-outputs scan-all-chans yes-or-no-p filter? mus-b1 scan-chan zero-pad filtering mus-b2 scan-chans zoom-color find mus-bshort scan-sound-chans zoom-focus-style find-mark mus-bshort seconds->samples find-sound mus-channel select-all finish-progress-report mus-channels select-channel FIR-filter mus-close select-mix