Snd Customization and Extension Part 2 |
|
|
Snd Startup |
Snd invocation flags |
Snd recognizes the following switches in its command line (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 /etc/snd.conf |
-noinit | don't read ~/.snd |
-nostdin | don't watch for possible input from stdin |
-p -preload <dir> | preload sound files in directory <dir> (snd -p .) |
-l -load <file> | load Scheme or Ruby code in <file> (snd -l test.scm) |
-e -eval expr | evaluate expr |
-b -batch <file> | load Scheme or Ruby code in <file> as a batch (no GUI) job |
-I <dir> | add <dir> to the load search list |
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:
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. The following adds "WAV" to the sound file extension table before preloading the directory:
snd -e '(add-sound-file-extension "WAV")' -p /home/bil/sounds
The initialization file |
When Snd starts, 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. Say we want the Snd window to start out 800x500, want to predefine an envelope named "env1", and want the file selection box to show 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 more complex situations, you may want an initialization file particular to a given machine, and global across users; the name of this optional global initialization file is "/etc/snd.conf". It is read before your 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. Here's a more elaborate initialization file:
(use-modules (ice-9 debug) (ice-9 format) (ice-9 optargs)) (debug-set! stack 0) ;turn off Guile's stack-size check (set! snd-remember-paths #t) ;remember paths making "load" smarter (set! (window-width) 800) ;these set the initial window size (set! (window-height) 500) (if (provided? 'snd-motif) ;Motif and Gtk use different font naming conventions (begin (set! (listener-font) "9x15") (set! (axis-label-font) "-*-times-medium-r-normal-*-18-*-*-*-*-*-*-*") (set! (axis-numbers-font) "9x15")) (begin (set! (listener-font) "Monospace 10") (set! (axis-label-font) "Serif 14") (set! (axis-numbers-font) "Monospace 10"))) (set! (listener-prompt) ":") ;change listener prompt from the default ">" to ":" (set! (show-listener) #t) ;include the listener window initially (set! (show-indices) #t) ;include sound index values with the sound name (define beige (make-color 0.96 0.96 0.86)) (define blue (make-color 0 0 1)) (set! (selected-graph-color) beige) ;selected graph background is beige (set! (selected-data-color) blue) ;selected graph data is blue (add-hook! mouse-enter-graph-hook ;automatically focus on (activate) the widget under the mouse (lambda (snd chn) (focus-widget (car (channel-widgets snd chn))))) (add-hook! mouse-enter-listener-hook (lambda (widget) (focus-widget widget))) (add-hook! mouse-enter-text-hook (lambda (w) (focus-widget w))) (set! (save-dir) "/zap/snd") ;save-state files are placed in /zap/snd (set! (temp-dir) "/zap/tmp") ;temp files are placed in /zap/tmp (load "peak-env.scm") ;large file peak env data is saved for faster ; subsequent reads -- this makes the first ; view of a large file much snappier -- ; its default directory for the data is ~/peaks. (load "draw.scm") ;load some useful extensions (load "hooks.scm") (load "extensions.scm") (make-current-window-display) ;display an overview of the current window in the upper right (if (provided? 'snd-motif) (load "popup.scm") ;context-sensitive popup menus (if (provided? 'snd-gtk) (load "/home/bil/cl/gtk-popup.scm"))) (add-hook! after-open-hook ;if sound has many chans, use just one pane for all (lambda (snd) (if (> (chans snd) 4) (set! (channel-style snd) channels-combined)))) (set! (optimization) 6) ;turn on full optimization (live dangerously...) (set! (selection-creates-region) #f) ;turn off automatic region creation |
If you loaded Snd with GSL, and have set the GSL_IEEE_MODE environment variable, it will override Snd's default arithmetic mode settings. GSL recommends the setting:
GSL_IEEE_MODE=double-precision,mask-underflow,mask-denormalized
For more complex initialization files, see snd_conffile.scm and edit123.scm.
Snd resources |
In the Motif version, there are a few X-style resources that Snd looks for (see Snd.ad); each has a built-in default value, so you can ignore any that are already acceptable:
initFile | "~/.snd" |
autoResize | 1 |
horizontalPanes | 0 |
peaksFont | -*-times-medium-r-*-*-14-*-*-*-*-*-*-* |
boldpeaksFont | -*-times-bold-r-*-*-14-*-*-*-*-*-*-* |
axisLabelFont | -*-times-medium-r-normal-*-20-*-*-*-*-*-*-* |
axisNumbersFont | -*-courier-medium-r-normal-*-14-*-*-*-*-*-*-* |
listenerFont | default font (fixed 7x13, I think) |
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 |
helpbuttoncolor | lightsteelblue2 |
quitbuttoncolor | indianred |
resetbuttoncolor | goldenrod1 |
doitbuttoncolor | palegreen2 |
doitagainbuttoncolor | darkolivegreen1 |
You can experiment with other choices by using the -xrm switch:
snd -xrm '*Highlightcolor: Red' oboe.snd snd -xrm '*AxisNumbersFont: 6x10' 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' snd -xrm '*fontList: -*-times-medium-r-*-*-14-*-*-*-*-*-*-*' snd -xrm '*fontList: -*-symbol-*-*-*-*-18-*-*-*-*-*-*-*'
Perhaps this is most useful when your window manager assumes white text for labels (menus); since Snd defaults to an off-white background, this makes everything unreadable. You can get the black text back with:
./snd -xrm '*foreground: Black'
or put the equivalent line in your .Xdefaults file. The listener text color can be set
via (set! (listener-text-color) (make-color 0 0 0))
in ~/.snd.
The colors are defined in rgb.scm. "useSchemes" refers to SGI color schemes. It's unfortunate that there is the language Scheme implemented by Guile, and the notion of an SGI color scheme -- there is no connection between the two. Other systems (Linux, Solaris) use the term "theme" instead. You can get Snd to honor your current window manager theme by setting useSchemes to all. The last example above 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 Snd.ad or in your .Xdefaults file:
snd*axisLabelFont: -*-times-medium-r-normal-*-18-*-*-*-*-*-*-* snd*axisNumbersFont: 9x15 snd*fontList: -*-times-bold-r-*-*-14-*-*-*-*-*-*-*
To find what fonts are available, and what they look like, I use xfontsel. I think other toolkits use helvetica where Motif defaults to a small fixed font -- it looks like 7x13 on my machine.
If you have the xg module loaded into the Gtk+ version of Snd, you can load snd-gtk.scm, and call make-font-selector-dialog or make-color-selector-dialog to goof around with the fonts and colors.
The autoResize resource determines how Snd acts when files are added to or removed from its overall display. The default (1) causes Snd to expand or contract the main window's size to accommodate 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 (to some extent the window manager controls this stuff). See also the variable auto-resize. 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.
In Gtk, the various default colors and fonts are set via the "gtkrc" file. If you are using some global "theme", you probably have ~/.gtkrc-2.0. Snd looks for this file first. If it isn't found, Snd looks on the current directory for Snd.gtkrc. If that isn't found, it looks for ~/Snd.gtkrc. If that isn't found, it uses a built-in default equivalent to the Snd.gtkrc file in the Snd tarball. If you want to change the overall appearance of Snd, you can edit any of these rc files. Currently, the simplest way to change the menu label fonts is to change the "default" or "default_menu" styles in Snd.gtkrc:
style "default" { font_name = "Sans Serif 11" .... }
Frank Barknecht made a more elaborate example using the Xfce-b5 theme:
And Kjetil Matheussen has put together the "snd-ls" package with many improvements:
The Snd-specific X (Motif) color resources are:
basiccolor | default background color everywhere | basic-color |
cursorcolor | color of the cursor | cursor-color |
datacolor | unselected data color | data-color |
doitbuttoncolor | color of Apply or Ok buttons | doit-button-color |
doitagainbuttoncolor | color of Undo&Apply buttons | doit-again-button-color |
envedwaveformcolor | color of envelope editor waveform | enved-waveform-color |
filterwaveformcolor | color of control panel filter waveform | filter-control-waveform-color |
graphcolor | unselected channels' graph background | graph-color |
helpbuttoncolor | color of Help buttons | help-button-color |
highlightcolor | highlighting here and there | highlight-color |
listenercolor | background color of the listener | listener-color |
listenertextcolor | text color in the listener | listener-text-color |
markcolor | color of the mark indicator | mark-color |
mixcolor | used for mix waveforms | mix-color |
positioncolor | color of position sliders | position-color |
pushedbuttoncolor | color of pushed button | pushed-button-color |
quitbuttoncolor | color of Cancel or Dismiss buttons | quit-button-color |
resetbuttoncolor | color of Reset buttons | reset-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 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 widget sizes: zoomSliderWidth, positionSliderWidth, toggleSize, sashSize, sashIndent, channelSashSize, and channelSashIndent. 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).
Configuration choices |
The configuration process is controlled by a set of switches, some specific to Snd. The latter are (configure --help):
Audio choices | (normally this is decided automatically) | |
--with-esd | use ESD (enlightened sound daemon) | |
--with-alsa | use ALSA (Linux OSS replacement) | |
--with-static-alsa | use ALSA statically loaded | |
--with-jack | use JACK (Linux audio stream sharing support, can be used with ALSA) | |
Graphics choices | ||
--with-gtk | use Gtk+ to build Snd (Gtk+ version 2.0 or later) | |
--with-motif | use Motif to build Snd (the default), Lesstif is not supported | |
--with-static-motif | use libXm.a to build Snd | |
--with-motif-prefix=PFX | where Motif is installed | |
--with-no-gui | make Snd without any graphics support | |
--with-static-xm | include the xm module at build time; this is easier than trying to dynamically load xm.so | |
--with-gl | include OpenGL support (spectrogram, etc) | |
--with-just-gl | include OpenGL support, but omit the GL/Guile/Ruby bindings | |
--with-editres | include editres in xm | |
--with-xpm | use xpm, default=yes | |
--with-xp | use xp, default=no; this is the Xprint library which is not tied into Snd at all | |
--with-builtin-gtkrc | include built-in gtkrc fallbacks (default=yes) (some don't like my happy pastel color scheme) | |
Language choices | ||
--with-guile | use Guile (default=yes), GUILE_CONFIG_path sets Guile location; version 1.3.4 or later | |
--with-ruby | try to use Ruby as the extension language; version 1.6.4 or later | |
--with-ruby-prefix=PFX | where Ruby is installed | |
--with-hobbit | include hobbit-style function arity checking (hobbit is a Scheme compiler) | |
Numerical choices | ||
--with-doubles | use doubles throughout (default=no) | |
--with-float-samples | use floats as the internal sample representation (default=no) | |
--with-sample-width=N | if not float samples, use N bits for ints (default=24, must be between 16 and 31) | |
Library choices | ||
--with-gsl | use GSL (Chebyshev window), default=yes if local C does not support complex trig | |
--with-static-gsl | use libgsl.a | |
--with-fftw | use fftw, default=yes; fallback fft is built-in | |
--with-ladspa | include support for LADSPA plugins (in Linux default=yes) | |
--with-midi | include sndlib midi module (default=no) | |
--with-shared-sndlib | try to load libsndlib.so, rather than building it into Snd | |
--with-modules | use this switch with --with-shared-sndlib if sndlib.so uses modules | |
--disable-deprecated | do not include any deprecated stuff from gtk, guile, sndlib, xen, clm, etc | |
Directory choices | ||
--with-temp-dir | directory to use for temp files (default: ".") | |
--with-save-dir | directory to use for saved-state files (default: ".") | |
--with-doc-dir | directory to search for documentation (html-dir, elaborate set of defaults) | |
Debugging etc | ||
--with-snd-as-widget | make Snd a loadable widget, not a standalone program | |
--with-profiling | include profiling machinery, default=no | |
--enable-snd-debug | include internal Snd debugging functions (default=no) | |
--disable-largefile | omit support for large (64-bit byte address) files | |
--disable-nls | do not use Native Language Support |
Depending on how Snd was configured, any of the following symbols may be on the *features* list (in Guile):
clm | clm module (always included) |
gl | OpenGL callable from Scheme/Ruby (--with-gl switch) |
snd-ladspa | LADSPA loaded (--with-ladspa, but also the default in Linux if ladspa.h can be found) |
sndlib | sndlib module (always included) |
snd-motif | Motif used as GUI toolkit (--with-motif, usually the default if Motif can be found) |
snd-gtk | Gtk+ used as GUI toolkit (--with-gtk) |
snd-nogui | No GUI built-in (--with-no-gui, or neither Motif nor Gtk+ found) |
snd-guile | Guile as extension language (--with-guile, usually the default if guile can be found) |
snd-ruby | Ruby as extension language (--with-ruby) |
snd-debug | internal debugging hooks included (--enable-snd-debug) |
snd | It's Snd, ferchrissake... (always included) |
xg | Gtk module (xg.c) included (--with-static-xm, or loaded dynamically) |
Xp | Xprint library included and available from Scheme/Ruby (--with-xp, the default if it exists) |
xm | Motif module (xm.c) included (--with-static-xm, or loaded dynamically) |
To check whether something is available in the current Snd, use provided?,
as in (if (provided? 'snd-gtk) ; do something involving gtk...
).
In Ruby, the equivalent is (if (provided? "snd-gtk")...)
.
Runtime modules and external programs |
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 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 is to make it possible to run Snd as a subjob of Emacs. The simplest way to enable that is to use inf-snd.el by Michael Scholz. It starts with a long and detailed commentary. A simpler alternative is Fernando Lopez-Lezcano's DotEmacs, which also has comments on its use.
Dynamically loaded modules |
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 <libguile.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(scm_long2num(hiho(scm_num2int(a, 0, "hiho")))); } void init_hiho() { /* this declares hiho within Guile calling the wrapper which calls the C function hiho */ scm_c_define_gsubr("hiho", 1, 0, 0, hiho_wrapper); }
Next compile your code into a shared object (this is aimed at 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.
As a slightly more useful example, let's import the Bessel J0 function from GSL (Gnu Scientific Library); in this case, we need to build Snd with GSL (the easiest way is to include the --with-gsl option to configure). Then make a file (say "gsl-ex.c"):
#include <libguile.h> #include <gsl/gsl_sf_bessel.h> static SCM scm_j0(SCM x) { /* calls GSL function gsl_sf_bessel_J0_e */ gsl_sf_result res; gsl_sf_bessel_J0_e(scm_num2dbl(x, "j0"), &res); return(scm_make_real(res.val)); } void init_gsl_j0(void) { /* links scm_j0 into Snd under the name j0 */ scm_c_define_gsubr("j0", 1, 0, 0, scm_j0); }
Now the usual compile, load, link into Snd sequence:
/home/bil/snd-4/ cc gsl-ex.c -c -Wall /home/bil/snd-4/ ld -shared gsl-ex.o -o gsl-ex.so -lguile /home/bil/snd-4/ ./snd >(define lib (dynamic-link "/home/bil/snd-4/gsl-ex.so")) #<unspecified> >(dynamic-call "init_gsl_j0" lib) #<unspecified> >(j0 0.0) 1.0 >(j0 2.0) 0.223890779141236 >(define (bes-fm dur freq amp ratio index) ;; bessel-FM from CLM (let* ((car-ph 0.0) (mod-ph 0.0) (car-incr (hz->radians freq)) (mod-incr (* ratio car-incr)) (ampenv (make-env :envelope '(0 0 25 1 75 1 100 0) :scaler amp :end dur)) (output (make-vct dur))) (vct-map! output (lambda () (let ((val (* (env ampenv) (j0 car-ph)))) (set! car-ph (+ car-ph car-incr (* index (j0 mod-ph)))) (set! mod-ph (+ mod-ph mod-incr)) val))) (vct->channel output 0 dur))) #<unspecified> >(bes-fm 22050 440 10.0 1.0 8.0)
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 <libguile.h> static SCM g_srate; static SCM srate_wrapper(SCM a) {return(scm_call_1(g_srate, a));} void init_srate(void) { scm_c_define_gsubr("my-srate", 1, 0, 0, srate_wrapper); g_srate = scm_symbol_value0("srate"); }
Alternatively, you can simply use scm_eval_str0:
scm_eval_str0("(recorder-dialog)"); scm_eval_str0("(open-sound \"oboe.snd\")"); srate = scm_num2int(scm_eval_str0("(srate)"), 0, "");
External Programs |
Any external program that knows about sound files can be used to perform editing operations in Snd. You 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.
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 (save-sound-as), start CLM, call the instrument passing it the input filename (just written by Snd), then pass CLM's output back to Snd. Snd replaces (via set-samples) the current data with the data our instrument wrote, as if it had incorporated that instrument as an editing operation from the beginning. We then delete the Snd output (the input to CLM).
Snd as a Widget |
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. See saw.c included with Snd.
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.
Snd and the CLM module |
The files clm.c, clm.h, and clm2xen.c implement CLM (a Common Lisp Music V implementation described in clm.html, available in clm-3.tar.gz at ccrma-ftp) as a Guile-loadable module. They are 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. 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)))))
Any CLM instrument or function can be used in this way to edit sounds. 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)))))))) |
Here 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-channel (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-channel'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")) |
CLM functions |
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 |
average | (gen input) | moving window average |
average? | (gen) | #t if gen is an average 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 |
continue-frame->file | (file) | reopen file for more output |
continue-sample->file | (file) | reopen file for more output |
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 |
In Scheme delay is a built-in syntactic form; it is renamed %delay here. | ||
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 |
Some versions of Guile define an unrelated function named filter. It is renamed %filter, if it is noticed in time. | ||
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 inval) | |
formant? | (gen) | #t if gen is formant generator |
frame* | (fr1 fr2 outfr) | element-wise multiply |
frame+ | (fr1 fr2 outfr) | element-wise add |
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 |
In Guile frame? is part of the debugger; it is renamed %frame? here. | ||
granulate | (gen input-function edit-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 type) | |
make-average | (size initial-contents initial-element) | |
make-asymmetric-fm | (frequency initial-phase r ratio) | |
make-comb | (scaler size initial-contents initial-element max-size type) | |
make-convolve | (input filter fft-size filter-size) | |
make-delay | (size initial-contents initial-element max-size type) | |
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-coeffs | (order spectr) | |
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 edit) | |
make-iir-filter | (order ycoeffs) | |
make-locsig | (degree distance reverb output revout channels type) | |
make-mixer | (chans &rest vals) | |
make-notch | (scaler size initial-contents initial-element max-size type) | |
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 envelope distribution) | |
make-rand-interp | (frequency amplitude envelope distribution) | |
make-readin | (file channel start) | |
make-sample->file | (name chans format type comment) | |
make-sawtooth-wave | (frequency amplitude initial-phase) | |
make-sine-summation | (frequency initial-phase n a ratio) | |
make-snd->sample | ((index #f)) | |
make-square-wave | (frequency amplitude initial-phase) | |
make-src | (input srate width) | |
make-ssb-am | (frequency order) | |
make-sum-of-cosines | (cosines frequency initial-phase) | |
make-sum-of-sines | (sines frequency initial-phase) | |
make-table-lookup | (frequency initial-phase wave size) | |
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 size) | |
make-waveshape | (frequency partials wave size) | |
make-xen->sample | (reader) | |
make-zpolar | (radius frequency) | |
mixer* | (mix1 mix2 outmx) | matrix multiply of mix1 and mix2 |
mixer+ | (mix1 mix2 outmx) | add mixers mix1 and mix2 |
mixer-ref | (mixer in out) | mix-scaler[in,out] |
mixer-scale | (mix1 scl outmx) | scale mixer mix1 by scl |
mixer-set! | (mixer in out val) | mix-scaler[in,out] = val |
mixer? | (gen) | #t if gen is mixer object |
move-locsig | (gen degree distance) | |
multiply-arrays | (arr1 arr2) | arr1[i] *= arr2[i] |
mus-array-print-length | () | how many array (vct) elements to print in mus-describe |
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-describe | (gen) | return description of gen |
mus-feedback | (gen) | feedback term of gen (simple filters) |
mus-feedforward | (gen) | feedforward term of gen (all-pass) |
mus-file-buffer-size | () | size of input/ouput buffers (default 8192) |
mus-file-name | (gen) | name of file assoicated with gen |
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-interp-type | (gen) | interpolation choice of gen |
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 mixerto scale/envelope all the various ins and outs. outfile can be a frame->file generator, and infile can be a file->frame generator. | ||
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-run | (gen arg1 arg2) | apply gen to args |
mus-scaler | (gen) | scaler of gen |
mus-rand-seed | (val) | random number generator seed (settable via set!) |
mus-srate | () | current sampling rate |
mus-xcoeffs | (gen index) | x coeff of filter at index |
mus-xcoeffs | (gen) | feedforward (FIR) coeffs of filter |
mus-ycoeffs | (gen index) | y coeff of filter at index |
mus-ycoeffs | (gen) | feedback (IIR) coeffs 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? | (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 analyze edit synthesize) | phase vocoder generator |
phase-vocoder? | (pv) | #t if pv is phase vocoder generator |
polar->rectangular | (rl im) | translate from polar to rectangular coordinates |
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 |
In Ruby rand is a built-in method; it is aliased to kernel_rand here. | ||
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->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 |
samples->seconds | (val) | use mus-srate to convert samples into seconds |
sawtooth-wave | (gen fm) | sawtooth-wave generator |
sawtooth-wave? | (gen) | #t if gen is sawtooth-wave generator |
seconds->samples | (val) | use mus-srate to convert seconds into samples |
sine-bank | (amps phases) | additive synthesis |
sine-summation | (gen fm) | sine-summation generator |
sine-summation? | (gen) | #t if gen is sine-summation generator |
snd->sample | (loc (chan 0)) | return sample from sound (index=snd) at loc |
snd->sample? | (gen) | #t if gen is snd->sample 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 |
ssb-am | (gen insig fm) | ssb-am |
ssb-am? | (gen) | #t if gen is ssb-am generator |
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 | (gen fm) | sum-of-sines |
sum-of-sines? | (gen) | #t if gen is sum-of-sines generator |
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 |
xen->sample | (loc chan) | return sample from xen func at loc in chan |
xen->sample? | (gen) | #t if gen is xen->sample generator |
In Snd, the default locsig interpolation type is handled via locsig-type, rather than CLM's global variable *clm-locsig-type*.
formant-bank is an optimization for a situation like cross synthesis (see examp.scm). It is assumed that you have a vector of formant generators, all reading a single input signal, and summing their outputs. The amplitude scalers are in a vct.
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-channel (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-channel (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-channel (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-channel (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-channel (moving-formant .01 '(0 1200 1 2400))) ;;; various "Forbidden Planet" sound effects: (define fp (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->channel out-data 0 len)))) ; (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 (channel->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) (channel->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. CLM-in-CL users will be disappointed with the CLM-in-Scheme performance; my tests indicate that interpreted Scheme (as in Snd currently) is about 30 to 100 times slower than CLM instruments using the run macro. That macro now exists in Snd; see the optimization variable for a brief discussion. Currently, I can speed up most CLM-related Scheme code by factors between 8 and 20. This is still slightly slower than CLM, but it's certainly an improvement!
You can load Rick Taube's CM into Snd as Scheme code:
snd -l /home/bil/test/cm-2.4.2/src/cm.scm
and all of CM is at your disposal! See also Snd and Common Music.
In most CLM instruments, including all those in clm-ins.scm, the assumption is that you're reading and writing a temp file, calling the instruments within with-sound. The special generators snd->sample and xen->sample provide a way to redirect the CLM input handlers (in-any in particular) to either a Snd sound (via its index), or an arbitrary function.
Instruments |
It's hard to decide what's an "instrument" in this context, but I think I'll treat it as something that can be called as a note in a notelist (say in with-sound) and generate its own sound. Test 23 in snd-test.scm has a with-sound that includes most of these instruments.
Instrument | Sources | Description |
---|---|---|
anoi | clm-ins.scm | noise reduction based on P Cook's Scrubber.m |
attract | clm-ins.scm | James McCarthy's choatic brass |
bes-fm | clm-ins.scm | FM analog using Bessel j0 rather than sin |
bigbird | bird.scm, bird.rb | more bird songs |
bird | bird.scm, bird.rb | bird songs |
bow | strad.scm, strad.rb | bowed string physical model (Juan Reyes, Michael Scholz) |
bowstr | prc95.scm | bowed string pysical model |
brass | prc95.scm | brass pysical model |
cellon | clm-ins.scm | S Krupowicz's feedback FM |
chain-dsps | examp.scm | instrument gets gen patch from its args |
clarinet | prc95.scm | clarinet pysical model |
drone, canter | clm-ins.scm | Peter Common's bagpipe |
exp-snd | clm-ins.scm | granulate to the max |
expfil | clm-ins.scm | granular synthesis interleaving two files |
flute | prc95.scm | flute pysical model |
fm-bell | clm-ins.scm, examp.rb | Mike McNabb's FM bell |
fm-drum | clm-ins.scm | Jan Mattox's FM dum |
fm-insect | clm-ins.scm | FM insect (katydid) |
fm-noise | noise.scm, noise.rb | noise maker |
fm-trumpet | clm-ins.scm | Dexter Morrill's FM trumpet |
fm-violin | v.scm, v.rb, fmv.scm, sndscm.html | violin-like sounds via FM |
fofins | clm-ins.scm | FOF synthesis |
fullmix | clm-ins.scm | complicated mixer |
freeverb | freeverb.scm | a reverberator |
gong | clm-ins.scm | Paul Weineke's FM gong |
gran-synth | clm-ins.scm | simple granular synthesis |
graphEq | clm-ins.scm | Marco Trevisani's semi-graphical equalizer |
jc-reverb | jcrev.scm, examp.rb | Chowning's old reverb |
jl-reverb | clm-ins.scm | cavernous version of jc-reverb |
lbj-piano | clm-ins.scm | additive synthesis piano (Doug Fulton) |
maraca, big-maraca | maraca.scm | Perry Cook's maraca physical model |
metal | clm-ins.scm | more FM by Perry |
nrev | clm-ins.scm | Mike McNabb's reverb |
p | piano.scm, piano.rb | Scott van Duyne's piano physical model |
pins | clm-ins.scm | Xavier Serra SMS-style spectral modeling |
pluck | clm-ins.scm | David Jaffe's Karplus-Strong (physical model) plucked string |
plucky | prc95.scm | plucked string pysical model |
pqw-vox | clm-ins.scm | vocal sounds from phase-quadrature waveshaping |
pqw | clm-ins.scm | phase-quadrature waveshaping |
resflt | clm-ins.scm | two-poles for resonances (Xavier Serra) |
reson | clm-ins.scm | parallel FM creating resonances |
rhodey, wurley, hammondoid | clm-ins.scm | FM stuff from Perry Cook |
scm-simp | clm.html | simple sine wave instrument |
scratch | clm-ins.scm | move around in sound via list of turn points and src |
singer | singer.scm | Perry Cook's vocal tract physical model |
spectra | clm-ins.scm | simple additive synthesis |
stereo-flute | clm-ins.scm | Nicky Hind's flute physical model |
touch-tone | clm-ins.scm | touch-tone telephone |
tubebell | clm-ins.scm | Perry Cook's FM tubular bell |
two-tab | clm-ins.scm | interpolate between two spectra |
vox | clm-ins.scm | voice using "leapfrog" FM-generated formants |
zc, zn, za | clm-ins.scm | interpolating delay effects |
Snd and Common Music |
You can load Common Music into Snd, but the connection to Snd's definstrument macro is not yet as tight as it is in the Common Lisp CLM:
> (load "/home/bil/test/cm-2.4.2/src/cm.scm") #<unspecified> > (load "ws.scm") #<unspecified> > (definstrument (simp beg dur freq amp) (let* ((o (make-oscil freq)) (st (inexact->exact (* beg (mus-srate)))) (nd (+ st (inexact->exact (* dur (mus-srate)))))) (run (lambda () (do ((i st (1+ i))) ((= i nd)) (outa i (* amp (oscil o)) *output*)))))) #<unspecified> > (formals->defobject '(simp beg dur freq amp)) (defobject simp () ((beg #:accessor object-time) dur freq amp) (#:parameters beg dur freq amp)) > (defobject simp () ((beg #:accessor object-time) dur freq amp) (#:parameters beg dur freq amp)) #<values ()> > (define (random-fn n) (process repeat n output (new simp :beg (now) :dur .1 :freq (between 220 880) :amp .1) wait .25)) #<unspecified> > (events (random-fn 10) "test.clm" 0 :output "test.snd") "test.clm" > (define hi (init-with-sound :output "test.snd")) #<unspecified> > (load "test.clm") #<unspecified> > (finish-with-sound hi) "test.snd"
Snd and Motif |
It is possible to add your own user-interface elements using the xm module included with Snd. 'make xm' should create a shared library named xm.so; you can load this at any time into Snd:
> (define hxm (dlopen "/home/bil/snd-7/xm.so")) #<unspecified> > (dlinit hxm "init_xm") #t
and now we have access to all of X and Motif. Alternatively, use the configure switch --with-static-xm, and the xm module will be included in the base Snd image. Here's a dialog window with a slider:
(define scale-dialog #f) (define current-scaler 1.0) (define (create-scale-dialog parent) (if (not (Widget? scale-dialog)) (let ((xdismiss (XmStringCreate "Dismiss" XmFONTLIST_DEFAULT_TAG)) (xhelp (XmStringCreate "Help" XmFONTLIST_DEFAULT_TAG)) (titlestr (XmStringCreate "Scaling" XmFONTLIST_DEFAULT_TAG))) (set! scale-dialog (XmCreateTemplateDialog parent "Scaling" (list XmNcancelLabelString xdismiss XmNhelpLabelString xhelp XmNautoUnmanage #f XmNdialogTitle titlestr XmNresizePolicy XmRESIZE_GROW XmNnoResize #f XmNtransient #f))) (XtAddCallback scale-dialog XmNcancelCallback (lambda (w context info) (XtUnmanageChild scale-dialog))) (XtAddCallback scale-dialog XmNhelpCallback (lambda (w context info) (snd-print "move the slider to affect the volume"))) (XmStringFree xhelp) (XmStringFree xdismiss) (XmStringFree titlestr) (let* ((mainform (XtCreateManagedWidget "formd" xmFormWidgetClass scale-dialog (list XmNleftAttachment XmATTACH_FORM XmNrightAttachment XmATTACH_FORM XmNtopAttachment XmATTACH_FORM XmNbottomAttachment XmATTACH_WIDGET XmNbottomWidget (XmMessageBoxGetChild scale-dialog XmDIALOG_SEPARATOR)))) (scale (XtCreateManagedWidget "" xmScaleWidgetClass mainform (list XmNorientation XmHORIZONTAL XmNshowValue #t XmNvalue 100 XmNmaximum 500 XmNdecimalPoints 2)))) (XtAddCallback scale XmNvalueChangedCallback (lambda (w context info) (set! current-scaler (/ (.value info) 100.0)))) (XtAddCallback scale XmNdragCallback (lambda (w context info) (set! current-scaler (/ (.value info) 100.0))))))) (XtManageChild scale-dialog)) (create-scale-dialog (cadr (main-widgets)))
In Ruby, this is:
$scale_dialog = false $current_scaler = 1.0 def create_scale_dialog(parent) if !RWidget?($scale_dialog) then xdismiss = RXmStringCreate("Dismiss", RXmFONTLIST_DEFAULT_TAG) xhelp = RXmStringCreate("Help", RXmFONTLIST_DEFAULT_TAG) titlestr = RXmStringCreate("Scaling", RXmFONTLIST_DEFAULT_TAG) $scale_dialog = RXmCreateTemplateDialog(parent, "Scaling", [RXmNcancelLabelString, xdismiss, RXmNhelpLabelString, xhelp, RXmNautoUnmanage, false, RXmNdialogTitle, titlestr, RXmNresizePolicy, RXmRESIZE_GROW, RXmNnoResize, false, RXmNtransient, false]) RXtAddCallback($scale_dialog, RXmNcancelCallback, Proc.new { |w, context, info| RXtUnmanageChild($scale_dialog)}) RXtAddCallback($scale_dialog, RXmNhelpCallback, Proc.new { |w, context, info| snd_print "move the slider to affect the volume"}) RXmStringFree xhelp RXmStringFree xdismiss RXmStringFree titlestr mainform = RXtCreateManagedWidget("formd", RxmFormWidgetClass, $scale_dialog, [RXmNleftAttachment, RXmATTACH_FORM, RXmNrightAttachment, RXmATTACH_FORM, RXmNtopAttachment, RXmATTACH_FORM, RXmNbottomAttachment, RXmATTACH_WIDGET, RXmNbottomWidget, RXmMessageBoxGetChild($scale_dialog, RXmDIALOG_SEPARATOR)]) scale = RXtCreateManagedWidget("", RxmScaleWidgetClass, mainform, [RXmNorientation, RXmHORIZONTAL, RXmNshowValue, true, RXmNvalue, 100, RXmNmaximum, 500, RXmNdecimalPoints, 2]) RXtAddCallback(scale, RXmNvalueChangedCallback, Proc.new { |w, context, info| $current_scaler = Rvalue(info) / 100.0}) RXtAddCallback(scale, RXmNdragCallback, Proc.new { |w, context, info| $current_scaler = Rvalue(info) / 100.0}) RXtManageChild $scale_dialog end end $Snd_widgets = main_widgets() create_scale_dialog $Snd_widgets[1]
All of Snd is at your disposal once this module is loaded. The next function installs our own file filtering procedure into the File:Open dialog (it uses match-sound-files from extensions.scm):
(define (install-searcher proc) (define (XmString->string str) (cadr (XmStringGetLtoR str XmFONTLIST_DEFAULT_TAG))) (define (XmStringTable->list st len) (XmStringTableUnparse st len #f XmCHARSET_TEXT XmCHARSET_TEXT #f 0 XmOUTPUT_ALL)) (define (list->XmStringTable strs) (XmStringTableParseStringArray strs (length strs) #f XmCHARSET_TEXT #f 0 #f)) (XtSetValues (let ((m (open-file-dialog #f))) ; make sure the dialog exists (list-ref (dialog-widgets) 6)) (list XmNfileSearchProc ; set dialog file search procedure (lambda (widget info) (let* ((dir (XmString->string (dir info))) ; directory string (files (match-sound-files proc dir)) ; list of matching files (fileTable (list->XmStringTable ; XmStringTable for XmNfileListItems (map (lambda (n) ; every file needs prepended dir (string-append dir n)) files)))) (XtSetValues widget ; change the list of files (list XmNfileListItems fileTable XmNfileListItemCount (length files) XmNlistUpdated #t))))))) ;(install-searcher (lambda (file) (= (mus-sound-srate file) 44100))) ;(install-searcher (lambda (file) (= (mus-sound-chans file) 4)))
Now click the 'Filter' button to see only those files that fit the procedure in the dialog's files list. See snd-motif.scm and popup.scm.
Snd and Gtk+ |
Here's the scale-dialog in xg/gtk:
(define scale-dialog #f) (define current-scaler 1.0) (define* (g_signal_connect obj name func #:optional data) ;; slightly obsolete ... (g_signal_connect_closure_by_id (list 'gpointer (cadr obj)) (g_signal_lookup name (G_OBJECT_TYPE (GTK_OBJECT obj))) 0 (g_cclosure_new func data (list 'GClosureNotify 0)) #f)) (define (create-scale-dialog parent) (if (not scale-dialog) (begin (set! scale-dialog (gtk_dialog_new)) (g_signal_connect scale-dialog "delete-event" (lambda (w ev info) (gtk_widget_hide w))) (gtk_window_set_title (GTK_WINDOW scale-dialog) "Scale") (gtk_widget_realize scale-dialog) (let ((dismiss (gtk_button_new_with_label "Dismiss")) (help (gtk_button_new_with_label "Help"))) (gtk_box_pack_start (GTK_BOX (.action_area (GTK_DIALOG scale-dialog))) dismiss #t #t 4) (gtk_box_pack_end (GTK_BOX (.action_area (GTK_DIALOG scale-dialog))) help #t #t 4) (g_signal_connect dismiss "clicked" (lambda (w info) (gtk_widget_hide scale-dialog))) (g_signal_connect help "clicked" (lambda (w info) (help-dialog "Scaler Dialog" "move the slider to affect the volume"))) (gtk_widget_show dismiss) (gtk_widget_show help) (let* ((adj (gtk_adjustment_new 0.0 0.0 1.01 0.01 0.01 .01)) (scale (gtk_hscale_new (GTK_ADJUSTMENT adj)))) (gtk_range_set_update_policy (GTK_RANGE (GTK_SCALE scale)) GTK_UPDATE_CONTINUOUS) (gtk_scale_set_draw_value (GTK_SCALE scale) #t) (gtk_scale_set_digits (GTK_SCALE scale) 2) (g_signal_connect adj "value_changed" (lambda (wadj info) (set! current-scaler (.value (GTK_ADJUSTMENT wadj))))) (gtk_box_pack_start (GTK_BOX (.vbox (GTK_DIALOG scale-dialog))) scale #f #f 6) (gtk_widget_show scale))))) (gtk_widget_show scale-dialog)) (create-scale-dialog (cadr (main-widgets)))
The only change from the C code is the addition of GTK_ADJUSTMENT in the scale value_changed callback -- currently the xg module assumes the first argument to the two-argument callback is a GtkWidget, so we have to cast a GtkAdjustment back to its original type.
Snd with no GUI and scripting |
If Snd is built without a graphical user interface (by specifying --with-no-gui to configure), it runs Guile's "repl" (read-eval-print loop), or Ruby's equivalent, with input from stdin. All the non-interface related functions are available, so you can do things like:
snd> (new-sound "new.snd") 0 snd> (load "v.scm") snd> (fm-violin 0 1 440 .1) -1 snd> (frames 0) 22050 snd> (play) #t snd> (exit)
Guile's repl has its own error handlers, different from the normal Snd handlers; name completion, if it exists at all, won't complete Snd names; there are undoubtedly other differences that I haven't noticed.
Since this version of Snd is the same as the guile program with Snd loaded, you can treat it as a scripting engine. For example, if you have an executable file with:
#!/home/bil/test/snd-7/snd -l !# (define a-test 32) (display "hiho") (newline)
it can be executed just like any such script.
/home/bil/test/snd-7/ script hiho :a-test 32 :(exit) /home/bil/test/snd-7/
The difference between this use of Snd, and using guile itself for scripts is that Snd uses the -l switch where guile would use -s. As noted above, you can use the -e switch to use Snd as a pure command-line program, and, of course, (exit) to drop back to the shell. Here's a script that doubles every sample in "oboe.snd" and writes the result as "test.snd":
#!/home/bil/test/snd-7/snd -l !# (open-sound "oboe.snd") (scale-by 2.0) (save-sound-as "test.snd") (exit)
The functions script-args and script-arg can be used to access the script's arguments, and if necessary (if not exiting) tell Snd to ignore arguments. script-args returns a list of strings giving the arguments. The first two are always "-l" and the script file name. The current argument is (script-arg). If you set this to a higher value, Snd will subsequently ignore the intevening arguments as it scans the startup arguments (see snd-test.scm).
#!/home/bil/test/snd-7/snd -l !# (if (= (length (script-args)) 2) ;i.e. ("-l" "script") (display "usage: script file-name...") (begin (open-sound (list-ref (script-args) (+ (script-arg) 1))) (scale-by 2.0) (save-sound-as "test.snd"))) (exit)
This either grumbles if no argument is given, or scales its argument sound by 2.0:
script pistol.snd
And we can run through the entire argument list, doubling all the sounds or whatever by using a do loop -- the following displays all the comments it finds:
#!/home/bil/cl/snd -l !# (use-modules (ice-9 format)) (if (= (length (script-args)) 2) ;i.e. ("-l" "script") (display "usage: script file-name...") (do ((arg (+ (script-arg) 1) (1+ arg))) ((= arg (length (script-args)))) (let ((name (list-ref (script-args) arg))) (display (format #f "~A: ~A~%" name (mus-sound-comment name)))))) (exit)
Say we save this as the file "comments".
/home/bil/cl/comments *.snd
If you like, you can use env:
#!/usr/bin/env snd !#
But if that works, so will:
#!snd -l !#
This scripting mechanism actually will work in any version of Snd; to keep the Snd window from popping up, use the -b (-batch) switch in place of -l. Here's another script; it looks for any sounds that are longer than 40 seconds in duration, and truncates them to 40 seconds:
#!/usr/local/bin/snd -l !# (if (= (length (script-args)) 2) (display "usage: trunc.scm file-name...") (do ((arg (+ (script-arg) 1) (1+ arg))) ((= arg (length (script-args)))) (let* ((name (list-ref (script-args) arg))) (if (> (mus-sound-duration name) 40.0) (let* ((ind (open-sound name))) (set! (frames ind) (* 40 (srate ind))) (save-sound ind) (close-sound ind)))))) (exit)
Here's a sndplay replacement script:
#!snd -b !# (play-and-wait (list-ref (script-args) (+ (script-arg) 1))) (exit)
And here's a script that splits a multi-channel file into a bunch of mono files:
#!snd -b !# (if (= (length (script-args)) 2) (display "usage: split.scm filename") (let* ((name (list-ref (script-args) (1+ (script-arg)))) (chns (mus-sound-chans name))) (if (> chns 1) (let ((ind (open-sound name))) (do ((i 0 (1+ i))) ((= i chns)) (display (format #f "~A.~D " name i)) (save-sound-as (format #f "~A.~D" name i) ind (header-type ind) (data-format ind) (srate ind) i)) (close-sound ind))))) (exit)
Snd with Ruby |
Ruby is an extension language described as an "object-oriented Perl".
It provides a different syntax from that of Guile/Scheme. In Ruby, all
the "-" are "_", "->" is "2", hooks and memo_sound have "$" prepended
(since they are global variables from Ruby's point of view), and all the constants are capitalized
(e.g. Autocorrelation). The generalized set! functions are replaced
by "set_" plus the base name (e.g. set_window_width), with arguments
reordered in some cases to place the optional values after the new value. That is,
(set! (sync snd) 1)
becomes set_sync(1, snd)
.
Hooks in Ruby (which have little or nothing to do with Ruby's "hookable variables")
are just procedures or nil, not lists of procedures as in Guile.
Here's the Ruby version of the init file given
above:
set_window_width 800 set_window_height 500 set_listener_font "9x15" set_axis_numbers_font "9x15" set_show_mix_waveforms true set_trap_segfault false set_show_backtrace true set_show_indices true set_listener_prompt ":" show_listener beige = make_color 0.96, 0.96, 0.86 blue = make_color 0, 0, 1 set_selected_graph_color beige set_selected_data_color blue
Procedures are created via Proc.new, so to set the open-hook to print the file name,
>$open_hook = Proc.new { |name| snd_print name } #<Proc:0x40221b84> >open_sound "oboe.snd" /home/bil/cl/oboe.snd 0
(The trailing "0" is the result of open_sound). The Guile hook list support procedures aren't included in Ruby -- simply set the variable to the procedure you want, or false to clear it.
Vcts and sound-data objects mixin "Comparable" and "Enumerable", and respond to various array-like methods:
>v1 = make_vct 4 #<vct[len=4]: 0.000 0.000 0.000 0.000> >v1[3] = 1.0 1.0 >v1.sort 0.00.00.01.0 # I don't know why it prints this way but ... >v1 #<vct[len=4]: 0.000 0.000 0.000 1.000> >v1.max 1.0
I'm thinking about making classes for things like sounds; you could then
have sound + sound
to mix, or sound * 2
to scale it,
a given channel could be treated as an array, accessed via sound[0, 12345]
and so on,
These are extremely easy to add, but I'd like to coordinate this with Guile's
object system. Keywords, CLM generic functions, and optional arguments work as in Scheme:
>osc = make_oscil(:frequency, 440) oscil freq: 440.000Hz, phase: 0.000 >oscil osc 0.0 >oscil osc 0.1250506192 >osc.frequency 440.0
Lists (from the Scheme point of view) are arrays (vectors) in Ruby, and various built-in Scheme functions such as car aren't predefined, so to set up the focusing hooks as described in mouse-enter-graph-hook we need to do something along these lines:
def car(v) v[0] end $mouse_enter_graph_hook = Proc.new {|snd, chn| if sound? snd then focus_widget car channel_widgets snd, chn end } $mouse_enter_listener_hook = Proc.new { |widget| focus_widget widget }
Here's one more example, a translation of display-energy in draw.scm:
def display_energy(snd, chn) ls = left_sample rs = right_sample data1 = make_graph_data(snd, chn) data = data1 if not vct? data data = data1[1] end len = vct_length data sr = srate snd y_max = y_zoom_slider(snd, chn) vct_multiply!(data, data) graph(data, "energy", ls / sr, rs / sr, 0.0, y_max * y_max, snd, chn, false) end # $lisp_graph_hook = Proc.new {|snd, chn| display_energy(snd, chn)}
In Ruby you make a symbol by prepending ":", so Guile's
(list 'KeySym (char->integer #\F))
becomes
[:KeySym, ?F]
In the listener, everything is line-oriented (that is, I'm not trying to catch incomplete expressions). And it appears that in Ruby, variables defined within a file are considered local to that file(?). The save state mechanism is incomplete, but the basic stuff works. The RubyInline module might serve as a run-macro replacement. My very informal timing tests indicate that Guile and Ruby run at the same speed. Michael Scholz has translated many of the Scheme files to Ruby -- see any of the .rb files.
Snd and LADSPA Plugins |
init-ladspa list-ladspa analyse-ladspa library plugin ladspa-descriptor library plugin apply-ladspa reader data duration origin ladspa-instantiate descriptor srate ladspa-activate descriptor handle ladspa-deactivate descriptor handle ladspa-cleanup descriptor handle ladspa-connect-port descriptor handle port vct ladspa-run descriptor handle count ladspa-run-adding descriptor handle count ladspa-set-run-adding-gain descriptor handle gain
Richard Furse has provided a module to support LADSPA plugins in Snd. Here is his documentation:
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 to a block of samples. 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").
Dave Phillips in Linux Audio Plug-Ins: A Look Into LADSPA adds this:
(apply-ladspa (make-sample-reader 57264) (list "cmt" "delay_5s" .3 .5) 32556 "ibm.wav")
"This sequence tells Snd to read a block of 32556 samples from the ibm.wav file, starting at sample number 57264, and apply the delay_5s LADSPA plug-in (Richard Furse's delay plug-in, also found in cmt.so) with a delay time of .3 seconds and a 50/50 dry/wet balance."
To help Snd find the plugin library, set either the Snd variable ladspa-dir or the environment variable LADSPA_PATH to the directory. If, for example, cmt.so is in /usr/local/lib/ladspa, (and you're using tcsh), then
setenv LADSPA_PATH /usr/local/lib/ladspa
or
(set! (ladspa-dir) "/usr/local/lib/ladspa")
Snd plugins may have any number of inputs and outputs; if more than one input is required, the first argument to apply-ladspa should be a list of readers:
(apply-ladspa (list (make-sample-reader 0 0 0) ;chan 0 (make-sample-reader 0 0 1)) ;chan 1 (list "cmt" "freeverb3" 0 .5 .5 .5 .5 .5) 100000 "freeverb")
The "regularized" version of apply-ladspa could be defined:
(define* (ladspa-channel ladspa-data #:optional nbeg ndur nsnd nchn nedpos) (let* ((beg (or nbeg 0)) (snd (or nsnd (selected-sound) (car (sounds)))) (chn (or nchn (selected-channel))) (dur (or ndur (- (frames snd chn) beg))) (edpos (or nedpos current-edit-position)) (reader (make-sample-reader beg snd chn 1 edpos))) (apply-ladspa reader ladspa-data dur "apply-ladspa") (free-sample-reader reader))) |
There are also functions to access the LADSPA descriptor directly:
(define ptr (ladspa-descriptor "amp" "amp_mono")) (.Label ptr) "amp_mono" (.Name ptr) "Mono Amplifier" (.Copyright ptr) "None" (.Maker ptr) "Richard Furse (LADSPA example plugins)" (.Properties ptr) 4 (.UniqueID ptr) 1048 (.PortNames ptr) ("Gain" "Input" "Output") (.PortRangeHints ptr) ((593 0.0 0.0) (0 0.0 0.0) (0 0.0 0.0)) (.PortCount ptr) 3 (.PortDescriptors ptr) (5 9 10) (logand (cadr (.PortDescriptors ptr)) LADSPA_PORT_INPUT) 1
See ladspa.h for full details. We could replace analyse-ladspa using these functions:
(define (analyze-ladspa library label) (let* ((descriptor (ladspa-descriptor library label)) (data '()) (names (.PortNames descriptor)) (hints (.PortRangeHints descriptor)) (descriptors (.PortDescriptors descriptor)) (name (.Name descriptor)) (maker (.Maker descriptor)) (copy (.Copyright descriptor))) (for-each (lambda (port ranges port-name) (if (and (not (= (logand port LADSPA_PORT_CONTROL) 0)) (not (= (logand port LADSPA_PORT_INPUT) 0))) (let ((ldata '()) (hint (car ranges)) (lo (cadr ranges)) (hi (caddr ranges))) (if (not (= (logand hint LADSPA_HINT_TOGGLED) 0)) (set! ldata (cons "toggle" ldata))) (if (not (= (logand hint LADSPA_HINT_LOGARITHMIC) 0)) (set! ldata (cons "logarithmic" ldata))) (if (not (= (logand hint LADSPA_HINT_INTEGER) 0)) (set! ldata (cons "integer" ldata))) (if (not (= (logand hint LADSPA_HINT_SAMPLE_RATE) 0)) (set! ldata (cons "sample_rate" ldata))) (if (not (= (logand hint LADSPA_HINT_BOUNDED_ABOVE) 0)) (begin (set! ldata (cons hi ldata)) (set! ldata (cons "maximum" ldata)))) (if (not (= (logand hint LADSPA_HINT_BOUNDED_BELOW) 0) ) (begin (set! ldata (cons lo ldata)) (set! ldata (cons "minimum" ldata)))) (set! ldata (cons port-name ldata)) (set! data (cons ldata data))))) descriptors hints names) (append (list name maker copy) data))) |
Here's a function that processes a channel of data through a plugin, sending the data directly to the DAC:
(define* (ladspa-it library label #:rest plugin-parameters) ;; (ladspa-it "cmt" "delay_5s" .3 .5) (init-ladspa) (let* ((descriptor (ladspa-descriptor library label)) (handle (ladspa-instantiate descriptor (srate))) (block-size 256) (in-block (make-vct block-size)) (out-block (make-vct block-size)) (len (frames)) (data (make-sound-data 1 block-size)) (audio-port (mus-audio-open-output mus-audio-default (srate) 1 mus-lshort (* block-size 2)))) (dynamic-wind (lambda () (let ((count 0)) (for-each (lambda (port) (if (not (= (logand port LADSPA_PORT_CONTROL) 0)) (let ((parameter (make-vct 1 (car plugin-parameters)))) (set! plugin-parameters (cdr plugin-parameters)) (ladspa-connect-port descriptor handle count parameter)) (if (not (= (logand port LADSPA_PORT_INPUT) 0)) (ladspa-connect-port descriptor handle count in-block) (ladspa-connect-port descriptor handle count out-block))) (set! count (1+ count))) (.PortDescriptors descriptor)))) (lambda () (ladspa-activate descriptor handle) (do ((i 0 (+ i block-size))) ((>= i len)) (vct-subseq (channel->vct i block-size) 0 block-size in-block) (ladspa-run descriptor handle block-size) (vct->sound-data out-block data 0) (mus-audio-write audio-port data block-size))) (lambda () (ladspa-deactivate descriptor handle) (mus-audio-close audio-port) (ladspa-cleanup descriptor handle))))) |
For a Ladspa GUI builder, see ladspa.scm. One slight "gotcha" in this area: the Snd configuration switch --with-doubles causes "vcts" to be arrays of doubles, but Ladspa plugins expect to see arrays of floats. This is a problem only if you're calling ladpsa-connect-port yourself.
Driving Snd remotely |
It is possible to send Snd arbitrary Scheme or Ruby code from any other program;
see the program sndctrl.c. 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 listener). To get
a response from Snd, use (set! (window-property consat name) command)
where
consat is the property name Snd should search for (to find the sending window),
name is the property to change (the X window property that Snd will change), and
command is the string
that replaces the current property value (which Snd will evaluate).
CLM's communication
with Snd function sends Snd this string:
"(set! (window-property \"CLM_VERSION\" \"CLM_COMMAND\") " str ")"
where str is the form to be evaluated within Snd. It then waits for a change to the CLM_COMMAND property, returning its value to the user. The send-snd function itself, similarly, looks for SND_VERSION and sets SND_COMMAND to str, which Snd subsequently notices.
Snd and OpenGL |
Snd can be used in conjunction with OpenGL. If it is configured with the switch --with-gl or --with-just-gl, the top level Snd shell is setup to handle OpenGL graphics; the GLXContext is (snd-glx-context). The GL-to-Scheme bindings are in gl.c, and follow the same name and type conventions of the Motif bindings in xm.c. Any of the Snd drawing area widgets (or your own) can receive GL graphics commands. Here is a translation of the SGI/xjournal glxmotif program:
(define (draw-it) (glXMakeCurrent (XtDisplay (cadr (main-widgets))) (XtWindow (car (channel-widgets))) (snd-glx-context)) (glEnable GL_DEPTH_TEST) (glDepthFunc GL_LEQUAL) (glClearDepth 1.0) (glClearColor 0.0 0.0 0.0 0.0) (glLoadIdentity) (gluPerspective 40.0 1.0 10.0 200.0) (glTranslatef 0.0 0.0 -50.0) (glRotatef -58.0 0.0 1.0 0.0) (let ((vals (XtVaGetValues (car (channel-widgets)) (list XmNwidth 0 XmNheight 0)))) (glViewport 0 0 (list-ref vals 1) (list-ref vals 3))) (glClear (logior GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT)) (glBegin GL_POLYGON) (glColor3f 0.0 0.0 0.0) (glVertex3f -10.0 -10.0 0.0) (glColor3f 0.7 0.7 0.7) (glVertex3f 10.0 -10.0 0.0) (glColor3f 1.0 1.0 1.0) (glVertex3f -10.0 10.0 0.0) (glEnd) (glBegin GL_POLYGON) (glColor3f 1.0 1.0 0.0) (glVertex3f 0.0 -10.0 -10.0) (glColor3f 0.0 1.0 0.7) (glVertex3f 0.0 -10.0 10.0) (glColor3f 0.0 0.0 1.0) (glVertex3f 0.0 5.0 -10.0) (glEnd) (glBegin GL_POLYGON) (glColor3f 1.0 1.0 0.0) (glVertex3f -10.0 6.0 4.0) (glColor3f 1.0 0.0 1.0) (glVertex3f -10.0 3.0 4.0) (glColor3f 0.0 0.0 1.0) (glVertex3f 4.0 -9.0 -10.0) (glColor3f 1.0 0.0 1.0) (glVertex3f 4.0 -6.0 -10.0) (glEnd) (glXSwapBuffers (XtDisplay (cadr (main-widgets))) (XtWindow (car (channel-widgets)))) (glFlush)) |
See snd-gl.scm.
Snd and gdb |
Here are some gdb functions (for your ~/.gbdinit file) that might come in handy:
define gp set gdb_print($arg0) print gdb_output end document gp Executes (object->string arg): gp memo_sound => #f end define ge call gdb_read($arg0) call gdb_eval(gdb_result) set gdb_print(gdb_result) print gdb_output end document ge Executes (print (eval (read arg))): ge "(+ 1 2)" => 3 end define gh call g_help(scm_str2symbol($arg0), 20) set gdb_print($1) print gdb_output end document gh Prints help string for arg: gh "enved-target" end
SCM values are displayed as integers in gdb, so say Snd halts and you notice it's loading some unknown file:
#32 0x081ae8f4 in scm_primitive_load (filename=1112137128) at load.c:129
You can get the file name with gp:
(gdb) gp 1112137128 $1 = 0 $2 = 0x40853fac "\"/home/bil/test/share/guile/1.5.0/ice-9/session.scm\""
If you have a pointer to a CLM generator, you can use:
p mus_describe(arg) -- show the outsider's view of arg
The ge function can show current Snd state:
(gdb) ge "(eps-file)" $5 = 0 $6 = 0 $7 = 0 $8 = 0x8296cf0 "\"snd.eps\""
|