Snd Customization and Extension Part 2


related documentation: snd.html extsnd.html sndscm.html clm.html sndlib.html libxm.html index.html
this file:extsnd.html:

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 -horizontallayout 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 (listener is 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> add sound files in directory <dir> to the View:Files list (snd -p .)
-l -load <file> load Scheme, Ruby, or Forth code in <file> (snd -l test.scm)
-e -eval expr evaluate expr
-b -batch <file> load Scheme, Ruby, or Forth code in <file> as a batch (no GUI) job
-I <dir> add <dir> to the load search list
-nogtkrc don't read ~/Snd.gtkrc or any of its friends (Gtk only)
notebook

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 adding all the sound files in the directory to the View:Files dialog's list:

    snd -e '(add-sound-file-extension "WAV")' -p /home/bil/sounds
black background

The initialization file

When Snd starts, it looks for an initialization file, normally named "~/.snd". This optional file is supposed to be similar to 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:

Scheme:
    (set! (window-width) 800)
    (set! (window-height) 500)
    (set! (just-sounds) #t)
    (define-envelope env1 '(0 0 1 1 2 0))

Ruby:
    set_window_width(800)
    set_window_height(500)
    set_just_sounds(true)
    define_envelope("env1", [0, 0, 1, 1, 2, 0])

Forth:
    800 set-window-width drop
    500 set-window-height drop
    #t set-just-sounds drop
    $" env1" '( 0.0 0.0 1.0 1.0 2.0 0.0 ) 1.0 define-envelope drop

In more complex situations, you may want an initialization file particular to a given extension language, machine, or 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.

The initialization files particular to a given extension language have names such as ~/.snd_guile and ~/.snd_prefs_ruby. Currently the possibilities are: ~/.snd_prefs_guile|ruby|forth|gauche, /etc/snd_guile|ruby|forth|gauche.conf, ~/.snd_guile|ruby|forth|gauche (the notation "guile|ruby|forth" means either "guile" or "ruby" or "forth", so the file names spelled out completely are "/etc/snd_guile.conf", "/etc/snd_ruby.conf", etc; which one is loaded obviously depends on which language you chose during configuration). The save-options (preferences dialog) process writes to .snd_prefs_* which is loaded first, if it exists; then Snd looks for .snd_* (guile, ruby, forth, or gauche); then .snd. If you're always using just one version of Snd, it's simplest to stick with .snd. The actual load sequence is:

    /etc/snd_guile|ruby|forth|gauche.conf    (these two canceled by -noglob)
    /etc/snd.conf               
    ~/.snd_prefs_guile|ruby|forth|gauche     (the rest canceled by -noinit)
    ~/.snd_guile|ruby|forth|gauche
    ~/.snd                                   (also SND_INIT_FILE_ENVRIONMENT_NAME)

Here's a more elaborate initialization file (~/.snd_guile):

(use-modules (ice-9 debug) (ice-9 format) (ice-9 optargs))
(debug-set! stack 0)                ;turn off Guile's stack-size check

(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

(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
(focus-follows-mouse)               ;automatically focus on (activate) the widget under the mouse

(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

A similar Ruby initialization file (~/.snd_ruby):

require "draw"
require "popup"

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_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
make_current_window_display

$mouse_enter_graph_hook.add_hook!("focus") do |snd, chn|
  focus_widget(channel_widgets(snd, chn)[0])
end
$mouse_enter_listener_hook.add_hook!("focus") do |widget| focus_widget(widget) end
$mouse_enter_text_hook.add_hook!("focus") do |widget| focus_widget(widget) end

And Forth:

\ .snd_forth -- start up file for Snd/Forth -*- snd-forth -*-

\ You can easily install the *.fs scripts with:
\ 
\   cd fth-0.8.x/examples/site-lib
\   ./install.fth
\
\ If you have installed the *.fs files with install.fth, you don't
\ need to add a path to *load-path*.  ${prefix}/share/fth/site-fth is
\ already included.  Otherwise you can add a path with e.g.
\
\   "/home/mike/snd-9" add-load-path

#t to *fth-verbose*

: verbose-loading ( fname -- f )
  $" \\ loading \"%s\"\n" swap 1 >list fth-print
  #t
;

'snd-nogui provided? [unless] before-load-hook ' verbose-loading 1 make-proc add-hook! [then]

require clm
require clm-ins
require env

[undefined] clm-print [if] ' fth-print alias clm-print [then]

#t to *clm-verbose*
lambda: ( str -- ) ." \ <" .string ." >" cr ; to *clm-notehook*

'snd-motif provided? 'xm provided? not && [if] dl-load libxm Init_libxm [then]
'snd-gtk   provided? 'xg provided? not && [if] dl-load libxg Init_libxg [then]
require peak-env
require examp
require extensions
require mix
require marks
require draw
'snd-motif provided? [if]
  require effects
  require popup
  edhist-save-hook lambda: ( prc -- )
    $" #<proc: %s>\n" swap 1 >list string-format .stdout
  ; 1 make-proc add-hook!
[then]

exit-hook lambda: ( -- f )
  save-state-file save-state drop
  #t
; 0 make-proc add-hook! 

after-open-hook lambda: ( snd -- val )
  { snd }
  snd channels 0 ?do snd short-file-name snd i time-graph set-x-axis-label drop loop
  #t snd set-cursor-follows-play drop
  channels-combined snd set-channel-style
; 1 make-proc add-hook!

after-save-as-hook lambda: ( snd fname from-dialog -- f )
  { snd fname from-dialog }
  snd revert-sound drop
  snd close-sound drop
  fname open-sound drop
  #f
; 3 make-proc add-hook!

start-playing-hook lambda: ( snd -- f )
  { snd }
  #f
  snd sound? if snd cursor-follows-play if drop cursor-line snd #t set-cursor-style then then
; 1 make-proc add-hook!

stop-playing-hook lambda: ( snd -- f )
  { snd }
  #f
  snd sound? if drop cursor-cross snd #t set-cursor-style then
; 1 make-proc add-hook!

window-property-changed-hook lambda: ( cmd -- )
  ." \ remote command received: " ( cmd ) .string cr
; 1 make-proc add-hook!

before-save-state-hook lambda: ( fname -- f )
  ( fname ) io-open-write dup $" \\ -*- snd-forth -*-\n" io-write io-close
  #t
; 1 make-proc add-hook!

enved-hook lambda: ( en pt x y reason -- en'|#f )
  { en pt x y reason }
  reason enved-move-point = if
    x en car f> x en -2 list-ref f< and if
      en en pt 2* list-ref x #f #f stretch-envelope { new-en }
      new-en pt 2* 1+ y list-set!
      new-en
    else
      #f
    then
  else
    #f
  then
; 5 make-proc add-hook!

\ from ~/.snd_prefs_forth
with-buffers-menu
with-reopen-menu
0.00 0.10 #t prefs-activate-initial-bounds
focus-follows-mouse
2 set-global-sync
#f check-for-unsaved-edits
3 remember-sound-state
'xm provided? [if]
  add-mark-pane
  make-current-window-display
  #t show-smpte-label
[then]
save-mark-properties
defined? use-combo-box-for-fft-size [if]
  #f to use-combo-box-for-fft-size
[then]

22050      	       set-default-output-srate drop
2          	       set-default-output-chans drop
mus-next   	       set-default-output-header-type drop
mus-lfloat 	       set-default-output-data-format drop
mus-interp-linear      set-locsig-type drop
512                    set-dac-size drop
mus-audio-default      set-audio-output-device drop
#t                     set-show-indices drop
0.0                    set-auto-update-interval drop
#f                     set-trap-segfault drop
$" rev"                add-sound-file-extension drop
$" reverb"             add-sound-file-extension drop
$" wave"               add-sound-file-extension drop
$" snd> "              set-listener-prompt drop
speed-control-as-ratio set-speed-control-style drop

'snd-nogui provided? [unless]
  13  			    	      set-colormap drop
  #f  			    	      set-show-listener drop
  #f  			    	      set-show-controls drop
  #f  			    	      set-just-sounds drop
  1.0 			    	      set-enved-base drop
  #t  			    	      set-enved-wave? drop
  #t  			    	      set-show-y-zero drop
  #t  			    	      set-verbose-cursor drop
  0.96 0.96 0.86 make-color ( beige ) set-selected-graph-color drop
  0.00 0.00 1.00 make-color ( blue )  set-selected-data-color drop
  #f                                  set-with-gl drop
  graph-once                          set-transform-graph-type drop
  #t                                  set-show-transform-peaks drop
  dolph-chebyshev-window              set-fft-window drop
  fourier-transform                   set-transform-type drop
  \ lisp-graph-hook ' display-db      2 make-proc add-hook!
  \ lisp-graph-hook ' display-energy  2 make-proc add-hook!
  mix-click-hook  ' mix-click-info  1 make-proc add-hook!
  mark-click-hook ' mark-click-info 1 make-proc add-hook!
  defined? show-disk-space [if]
    after-open-hook ' show-disk-space 1 make-proc add-hook!
  [then]
  #t  			   	      set-show-listener drop
  #f  			   	      set-show-controls drop
  160 			   	      set-window-x drop
  000 			   	      set-window-y drop
  800 			   	      set-window-width drop
  600 			   	      set-window-height drop
[then]

[ifundef] enved
  $" enved"    '( 0.0 0.0 25.0 1.0 75.0 1.0 100.0 0.0 )           1.0  define-envelope drop
  $" brass"    '( 0.0 0.0 20.0 1.0 40.0 0.6 90.0 0.5 100.0 0.0 )  1.0  define-envelope drop
  $" bassoon"  '( 0.0 0.0 10.0 1.0 90.0 1.0 100.0 0.0 )           1.0  define-envelope drop
  $" clarinet" '( 0.0 0.0 25.0 1.0 75.0 1.0 100.0 0.0 )          32.0  define-envelope drop
  $" woodwind" '( 0.0 0.0 10.0 1.0 90.0 1.0 100.0 0.0 )           0.32 define-envelope drop
[then]

$" fth" textdomain drop

\ .snd_forth ends here

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, snd_frg.scm, and edit123.scm.

Normally, the initialization file also adds the Snd sources directory to the load path (see README.Snd or the introduction to Snd.html). For more temporary situations, you can use the environment variable SND_PATH. It is a colon-separated list of directories that will be added to the load path when Snd starts.

In Gauche, before loading any of the Snd *.scm files, you need to load gauche-optargs.scm and gauche-format.scm:

    (if (not (provided? 'gauche-optargs.scm)) (load "gauche-optargs.scm"))
    (if (not (provided? 'gauche-format.scm)) (load "gauche-format.scm"))

Snd resources

Motif

In the Motif version, there are a few X-style resources (for use in your ~/.Xdefaults file) that Snd looks for (see Snd.ad); each has a built-in default value, so you can ignore this entirely, and use the extension language initialization file to choose new values. I may remove all this stuff eventually (it was always a bad idea).


Resource NameDefault ValuePurposeScheme Name


autoResize1should Snd window resize automaticallyauto-resize
horizontalPanes0basic paned window layout


peaksFonttimes 14fft peaks fontpeaks-font
boldpeaksFonttimes bold 14main fft peaks fontbold-peaks-font
axisLabelFonttimes 20axis label ("Time")axis-label-font
axisNumbersFontcourier 14axis tick numbersaxis-numbers-font
listenerFontfixed 7x13listener textlistener-font


basiccolorivory2default background color everywherebasic-color
cursorcolorred color of the cursor cursor-color
datacolorblackunselected data color data-color
graphcolorwhite unselected channels' graph background graph-color
highlightcolorivory1 highlighting here and there highlight-color
listenercoloraliceblue background color of the listener listener-color
listenertextcolorblack text color in the listener listener-text-color
markcolorred color of the mark indicator mark-color
mixcolordarkgray used for mix waveforms mix-color
positioncolorivory3 color of position sliders position-color
pushedbuttoncolorivory1 color of pushed button pushed-button-color
sashcolorlightgreen color of paned window sash handles sash-color
selecteddatacolorblack color of the data in selected channel selected-data-color
selectedgraphcolorwhite background of selected channel's graphselected-graph-color
selectioncolorlightsteelblue1 color of an active selection selection-color
textfocuscolorwhite color of text field with focus text-focus-color
zoomcolorivory4 color of zoom sliders zoom-color

In the extension language, 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.

help-button-color and friends only work in a few cases (Motif, 2.n where n < 3), so I may deprecate them someday.

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, redcolor (buttons, VU clipping, etc), greencolor (a few buttons), yellowcolor (a few envelope editor buttons), lightbluecolor, and lighterbluecolor (the fft option panel).

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 '*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 -xrm '*fontList: -*-times-medium-r-*-*-14-*-*-*-*-*-*-*'
    snd -xrm '*fontList: -*-symbol-*-*-*-*-18-*-*-*-*-*-*-*'
    snd oboe.snd -title hiho -display fatty.hiho:0.0 -xrm '*chn-graph*backgroundPixmap: text.xpm'

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 last example above sets the window title to "hiho", rather than "snd", displays the window on the machine fatty.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.

Gtk

In Gtk, the various default colors and fonts are set via the "gtkrc" file(s). 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"
      ....
    }

To get the "brushed metal" look, for example, put the line:

pixmap_path "/usr/share/themes/BrushedMetalClean/gtk"

at the start of Snd.gtkrc, then add these lines:

  bg_pixmap[NORMAL]      = "bc.xpm"
  bg_pixmap[PRELIGHT]    = "bc-light.xpm"
  bg_pixmap[ACTIVE]      = "bc-dark.xpm"
  bg_pixmap[INSENSITIVE] = "bc.xpm"

to the "default" section (the first style). Frank Barknecht made a more elaborate example using the Xfce-b5 theme:

Snd with Xfce-b5 theme

And Kjetil Matheussen has put together the "snd-ls" package with many improvements:


snd-ls

To get the default theme (set by Gnome or whatever), use the -nogtkrc startup switch. This is especially useful if you're using an inverse video theme.




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-esduse ESD (enlightened sound daemon)
--with-alsause ALSA (Linux OSS replacement)
--with-ossuse OSS (the default)
--with-static-alsause ALSA statically loaded
--with-jackuse JACK (Linux audio stream sharing support, can be used with ALSA)
Graphics choices
--with-gtkuse Gtk+ to build Snd (Gtk+ version 2.0 or later)
--with-motifuse Motif (version 2.0 or later) to build Snd (the default), Lesstif is not supported
--with-static-motifuse libXm.a to build Snd
--with-motif-prefix=PFXwhere Motif is installed
--with-no-guimake Snd without any graphics support
--with-static-xminclude the xm module at build time; this is easier than trying to dynamically load xm.so
--with-static-xginclude the xg module at build time; this is easier than trying to dynamically load xg.so
--with-glinclude OpenGL support (spectrogram, etc)
--with-just-glinclude OpenGL support, but omit the GL/Guile/Gauche/Ruby/Forth bindings
--with-gl2psinclude gl2ps (GL to Postscript code)
--with-editresinclude editres in xm (default: no)
--with-xpmuse xpm, default: yes
--with-xpuse xp, default: no; this is the Xprint library which is not tied into Snd at all
--with-builtin-gtkrcinclude built-in gtkrc fallbacks (default: yes)
--with-cairouse cairo for Gtk graphics if possible (work in progress)
Language choices
--with-guileuse Guile (default: yes), GUILE_CONFIG_path sets Guile location; version 1.3.4 or later
--with-forthtry to use Forth as the extension language
--with-rubytry to use Ruby as the extension language; version 1.6.4 or later
--with-gauchetry to use Gauche as the extension language; version 0.8.7 or later
--with-ruby-prefix=PFXwhere Ruby is installed
--with-hobbitinclude hobbit-style function arity checking (hobbit is a Scheme compiler), default: no
Numerical choices
--with-doublesuse doubles throughout (default: no)
--with-float-samplesuse floats or doubles as the internal sample representation (default: yes)
--with-sample-width=Nif not float samples, use N bits for ints (default: 24, must be between 16 and 31)
Library choices
--with-gsluse GSL (for Chebyshev window), default: yes if local C does not support complex trig
--with-static-gsluse libgsl.a (only for obsolete RPM stuff)
--with-fftwuse fftw, default: yes; fallback fft is built-in
--with-famuse libfam (Gamin), default: yes; fallback is polling via auto-update-interval
--with-ladspainclude support for LADSPA plugins (in Linux default: yes)
--with-midiinclude sndlib midi module (default: no)
--with-shared-sndlibtry to load libsndlib.so, rather than building it into Snd (default: no)
--with-modulesuse this switch with --with-shared-sndlib if sndlib.so uses modules
--disable-deprecateddo not include any deprecated stuff from gtk, guile, sndlib, xen, clm, etc
--enable-threadsinclude pthreads to take advantage of multiprocessor machines
Directory choices
--with-temp-dirdirectory to use for temp files (default: ".")
--with-save-dirdirectory to use for saved-state files (default: ".")
--with-doc-dirdirectory to search for documentation (html-dir, elaborate set of defaults)
Debugging etc
--with-snd-as-widgetmake Snd a loadable widget, not a standalone program
--with-snd-as-pd-externalmake Snd a loadable pd external, not a standalone program
--with-profilinginclude profiling (branch counting) machinery, default: no (only for debugging)
--enable-snd-debuginclude internal Snd debugging functions, default: no (not useful if not actually debugging!)
--disable-largefileomit support for large (64-bit byte address) files
--disable-nlsdo not use Native Language Support

Other configuration variables are GUILE_CONFIG_name and SNDLIB_CONFIG_path. The default configuration/build sequence:

  ./configure
  make
  make install

tries to use Guile, Motif, OSS (if Linux), and 32-bit float sample representation, then installs the snd executable in /usr/local/bin, a brief blurb in /usr/local/man/man1, and the German translations in whatever directory the translation files are sent to (this is part of the NLS business in Gnu). Here at CCRMA, we normally use:

  ./configure --with-alsa --with-temp-dir=/zap --with-static-xm --with-gl

and, since I'm usually running the CVS Guile, I use:

  ./configure CFLAGS="-Wall" GUILE_CONFIG_path=/home/bil/test/bin --with-gl --with-static-xm

There are many more examples in tools/compsnd. Don't use the --enable-snd-debug switch unless you know what you are asking for! Depending on how Snd is configured, any of the following symbols may be "provided" (on the *features* list in Guile/Gauche):

clmclm module (always included -- once upon a time it was optional)
glOpenGL callable from Scheme/Ruby/Forth (--with-gl switch)
snd-ladspaLADSPA loaded (--with-ladspa, but also the default in Linux if ladspa.h can be found)
sndlibsndlib module (always included)
snd-motifMotif used as GUI toolkit (--with-motif, usually the default if Motif can be found)
snd-gtkGtk+ used as GUI toolkit (--with-gtk)
snd-noguiNo GUI built-in (--with-no-gui, or neither Motif nor Gtk+ found)
snd-guileGuile as extension language (--with-guile, usually the default if guile can be found)
snd-rubyRuby as extension language (--with-ruby)
snd-forthForth as extension language (--with-forth)
snd-gaucheGauche as extension language (--with-gauche)
snd-debuginternal debugging hooks included (--enable-snd-debug)
sndIt's Snd, ferchrissake... (always included)
xgGtk module (xg.c) included (--with-static-xg, or loaded dynamically)
XpXprint library included and available from Scheme/Ruby/Forth (--with-xp, the default if it exists)
xmMotif module (xm.c) included (--with-static-xm, or loaded dynamically)
alsaALSA is in use, rather than OSS (--with-alsa)
gslGSL is loaded (--with-gsl)
gl2psgl2ps is included (--with-gl2ps)
cairocairo used for graphics (--with-cairo)

To check whether something is available in the current Snd, use:

    Scheme:  (provided? 'snd-gtk)
    Ruby:    provided? :snd-gtk
    Forth:   "snd-gtk" defined?

Environment variables

There are several environment variables specific to Snd:

SND_PATHSnd source load path list (a colon separated directory list)
SND_INIT_FILE_ENVIRONMENT_NAMEinit file name
LADSPA_PATHLadspa modules directory
MUS_ALSA_PLAYBACK_DEVICEname of playback device (Alsa only)
MUS_ALSA_CAPTURE_DEVICEname of capture device (Alsa only)
MUS_ALSA_DEVICEname of the playback and capture device (Alsa only)
MUS_ALSA_BUFFERSnumber of "periods" used (Alsa only)
MUS_ALSA_BUFFER_SIZEnumber of samples per channel per buffer (Alsa only)
AUDIODEVaudio device name (Sun and related drivers)

Runtime modules and external programs


Snd as an Emacs subjob

Snd watches stdin; any input received is evaluated as if typed in Snd's listener; any subsequent output is sent to stdout. Presumably any process could communicate with Snd in this manner, but the real reason for this 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.


Dynamically loaded modules

You can import shared object files into Snd at any time. If you have a Guile-aware module with a known initialization function:

  (define lib (dynamic-link "/home/bil/cl/cscm.so"))
  (dynamic-call "init_hiho" lib)

I used to have an explicit example here, but the Guile API changes so often, and so radically, that it was never up-to-date. Good luck!


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:

  GtkWidget *snd_as_widget(int argc, char **argv, GtkWidget *parent, void (*error_func)(const char *msg))

Snd and CLM

The files clm.c, clm.h, and clm2xen.c implement CLM (a Music V implementation), included in Snd when it is built. You can see what a generator does, or a group of generators, by running them in the 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-gen func)        ; func is a function of no arguments (a "thunk")
  (do ((i 0 (1+ i))) 
      ((= i data-size))
    (vct-set! data i (func))) ; fill data vct with output of 'func'
  (graph data))               ; display data as a graph

(define (run-fft func)
  (do ((i 0 (1+ i))) 
      ((= i data-size))
    (vct-set! data i (func)))
  (graph (snd-spectrum data blackman2-window data-size #t)))

Now we can open the listener, and type:

    (define hi (make-oscil))
    (run-gen (lambda () (oscil hi)))
    (define ho (make-oscil))
    (run-fft (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.

If we want "pre-echoes" instead (echoes of the future):

    (reverse-sound)
    (map-channel (echo .5 .75) 0 44100)
    (reverse-sound)

The Common Lisp version is:

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

Generators are "applicable" in most versions of Snd: the generator knows its type, so the explicit "oscil" function (for example) isn't needed.

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

We can make a generator that is either an oscil or a sawtooth-wave:

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

clm.html has lots of information about CLM. For historical reasons, it is oriented toward Common Lisp, rather than Scheme, but it should not be hard to move back and forth. There are a few minor differences, mostly involving global variable names. For example, in Snd, the default locsig interpolation type is handled via locsig-type, rather than CLM's global variable *clm-locsig-type*.

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. The Snd/Scheme version of an instrument (using 'run', of course) is slightly slower than the Common Lisp version, but not so much as to make it unusable.

You can load Rick Taube's CM into Snd as Scheme code:

    snd -l /home/bil/test/cm-2.8.0/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 generator snd->sample provides a way to redirect the CLM input handlers (in-any in particular) to a Snd sound (via its index).


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.

InstrumentSourcesDescription
anoiclm-ins.scm, clm-ins.rbnoise reduction based on P Cook's Scrubber.m
attractclm-ins.scm, clm-ins.rbJames McCarthy's chaotic brass
bes-fmclm-ins.scmFM analog using Bessel j1 rather than sin
bigbirdbird.scm, bird.rbmore bird songs
birdbird.scm, bird.rbbird songs
bowstrad.scm, strad.rbbowed string physical model (Juan Reyes, Michael Scholz)
bowstrprc95.scm, prc95.rbbowed string physical model
brassprc95.scmbrass physical model
cellonclm-ins.scm, clm-ins.rbS Krupowicz's feedback FM
chain-dspsexamp.scminstrument gets gen patch from its args
clarinetprc95.scmclarinet physical model
drone, canterclm-ins.scm, clm-ins.rbPeter Common's bagpipe
exp-sndclm-ins.scm, clm-ins.rbgranulate to the max
expfilclm-ins.scm, clm-ins.rbgranular synthesis interleaving two files
fluteprc95.scm, clm-ins.rbflute physical model
fm-bellclm-ins.scm, examp.rbMike McNabb's FM bell
fm-drumclm-ins.scm, clm-ins.rbJan Mattox's FM dum
fm-insectclm-ins.scm, clm-ins.rbFM insect (katydid)
fm-noisenoise.scm, noise.rbnoise maker
fm-trumpetclm-ins.scm, clm-ins.rbDexter Morrill's FM trumpet
fm-violinv.scm, v.rb, fmv.scm, sndscm.htmlviolin-like sounds via FM
fofinsclm-ins.scm, clm-ins.rbFOF synthesis
fullmixclm-ins.scmcomplicated mixer
freeverbfreeverb.scm, freeverb.rba reverberator
gongclm-ins.scm, clm-ins.rbPaul Weineke's FM gong
gran-synthclm-ins.scm, clm-ins.rbsimple granular synthesis
graphEqclm-ins.scm, clm-ins.rbMarco Trevisani's semi-graphical equalizer
jc-reverbjcrev.scm, examp.rbChowning's old reverb
jl-reverbclm-ins.scm, clm-ins.rbcavernous version of jc-reverb
lbj-pianoclm-ins.scm, clm-ins.rbadditive synthesis piano (Doug Fulton)
maraca, big-maracamaraca.scm, maraca.rbPerry Cook's maraca physical model
metalclm-ins.scm, clm-ins.rbmore FM by Perry
nrevclm-ins.scm, clm-ins.rbMike McNabb's reverb
ppiano.scm, piano.rbScott van Duyne's piano physical model
pinsclm-ins.scm, clm-ins.rbXavier Serra SMS-style spectral modeling
pluckclm-ins.scm, clm-ins.rbDavid Jaffe's Karplus-Strong (physical model) plucked string
pluckyprc95.scmplucked string physical model
pqw-voxclm-ins.scm, clm-ins.rbvocal sounds from phase-quadrature waveshaping
pqwclm-ins.scm, clm-ins.rbphase-quadrature waveshaping
resfltclm-ins.scmtwo-poles for resonances (Xavier Serra)
resonclm-ins.scm, clm-ins.rbparallel FM creating resonances
rhodey, wurley, hammondoidclm-ins.scm, clm-ins.rbFM stuff from Perry Cook
scratchclm-ins.scm, clm-ins.rbmove around in sound via list of turn points and src
singersinger.scm, singer.rbPerry Cook's vocal tract physical model
spectraclm-ins.scm, clm-ins.rbsimple additive synthesis
stereo-fluteclm-ins.scm, clm-ins.rbNicky Hind's flute physical model
touch-toneclm-ins.scm, clm-ins.rbtouch-tone telephone
tubebellclm-ins.scm, clm-ins.rbPerry Cook's FM tubular bell
two-tabclm-ins.scm, clm-ins.rbinterpolate between two spectra
voxclm-ins.scm, clm-ins.rbvoice using "leapfrog" FM-generated formants
zc, zn, zaclm-ins.scm, clm-ins.rbinterpolating delay effects


Snd and Common Music

You can load Common Music into Snd. The only real "gotcha" is that Snd's ws.scm has to be loaded before cm's clm2.scm. I load ws.scm in my init file, but in the following example, I'll start -noinit, and load it explicitly.

> (load "ws.scm")
#<unspecified>
> (load "/home/bil/test/cm/src/cm.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*))))))
#<values ()>

If the result of the definstrument call is #<unspecified> then cm's definstrument-hook isn't loaded into Snd's definstrument; if you go on anyway, you'll get some complaint that there is no class named "simp".

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

and the result ("test.snd") is opened in Snd.



Snd and Pd

Snd can be built to be used as a Pd "external". Use the --with-snd-as-pd-external switch, and make sure the configure script can find Guile 1.7.n. If you have several versions of Guile, use the GUILE_CONFIG_path variable to select the latest:

./configure --with-snd-as-pd-external GUILE_CONFIG_path=/home/bil/test/bin

help-snd.pd and snd-help-fm.pd have example patches. Here are brief descriptions of the functions:

(pd-inlet inlet-num type func)
    'func' is a function that is called when the object receives a message to the inlet 'inlet-num' of type 
      'type'. Normal values for 'type' are 'float, 'list, 'bang, etc. 
    add.scm, inout.scm 
    Local function. 

(pd-outlet outlet-num arg0 arg1 ...) 
    Sends one or more values to outlet 'outlet-num'. The arguments can be of any kind. 
    add.scm, inout.scm 
    Local function. 

(pd-bind symbol func) 
    Pd messages sent to 'symbol' arrive at the 'func' function. 
    send_receive.scm 
    Both local and global 

(pd-unbind symbol) 
    Stop receiving messages sent to 'symbol'. 
    Both local and global. For the local version, all bindings are automatically unbound when the object is destroyed or reloaded. 

(pd-send symbol arg0 arg1 ...) 
    Sends one or more values to receivers for 'symbol'. 'symbol' can either be a scheme or a pd symbol. 
    send_receive.scm 
    Global function. 

(pd-get-symbol symbol) 
    Returns the pd symbol for the scheme symbol 'symbol'. pd-send works faster when a pd symbol is used instead of a scheme symbol. 
    send_receive.scm 
    Global function. 

(pd-set-destroy-func thunk) 
    'thunk' is called before the object is destroyed or reloaded. 
    Local function.

snd with pd


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-9/xm.so"))
    #<unspecified>
    > (dlinit hxm "Init_libxm")
    #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)))

which creates a little dialog:

scale dialog

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+

There are tons of examples of using the gtk module. snd-gtk.scm and gtk-popup.scm might be places to start. All of gtk, gdk, and cairo, and much of pango and glib are accessible from the extension language if you loaded the xg module or built Snd with the configuration switch --with-static-xg. Here's the scale-dialog in xg/gtk:

(define scale-dialog #f)
(define current-scaler 1.0)

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

Here are a couple other examples of things that aren't covered by the gtk code in snd-gtk.scm and friends:

;; report on mouse scroll wheel changes when in the channel graph
(g_signal_connect 
 (car (channel-widgets)) "scroll_event" 
 (lambda (w e i)
   (snd-print (format #f "~%state: ~A, x: ~A, y: ~A, direction: ~A" 
		      (.state (GDK_EVENT_SCROLL e))
		      (.x (GDK_EVENT_SCROLL e))
		      (.y (GDK_EVENT_SCROLL e))
		      (.direction (GDK_EVENT_SCROLL e))))
   #f))

;; report which kind of click was received in the channel graph
(g_signal_connect 
 (car (channel-widgets)) "button_press_event"
 (lambda (w e i)
   (snd-print (format #f "~%button: ~A, ~A click" 
		      (.type (GDK_EVENT_BUTTON e))
		      (if (= (.type (GDK_EVENT_BUTTON e)) GDK_BUTTON_PRESS)
			  "single"
			  (if (= (.type (GDK_EVENT_BUTTON e)) GDK_2BUTTON_PRESS)
			      "double"
			      (if (= (.type (GDK_EVENT_BUTTON e)) GDK_3BUTTON_PRESS)
				  "triple"
				  "unknown")))))
   #f))

;; a double click is reported as single/single/double

;; double-click time in milliseconds(?)
(define (double-click-time)
  (g_object_get (GPOINTER 
                  (gtk_settings_get_for_screen 
                    (gdk_display_get_default_screen 
                      (gdk_display_get_default)))) 
                "gtk-double-click-time" #f))

Snd with no GUI and as scripting engine

If Snd is built without a graphical user interface (by specifying --with-no-gui to configure), it runs the extension language's read-eval-print loop, with input from stdin. All the non-interface related functions are available, so you can do things like:

SchemeRubyForth
snd
:(new-sound "test.snd")
0
:(mix "oboe.snd")
0
:(frames)
50828
:(play)
#f
:(exit)
snd
> new_sound("test.snd")
0
> mix("oboe.snd")
-1
> frames
50828
> play
false
> exit
snd
"test.snd" new-sound
0 ok
"oboe.snd" mix
-1 ok
frames
50828 ok
play
#f ok
snd-exit

Since there's no graphics toolkit event loop to get in your way, you can treat this version of Snd as a scripting engine. For example, if you have an executable file with:

#!/home/bil/test/snd-9/snd -l
!#
(define a-test 32)
(display "hiho")
(newline)

it can be executed just like any such script.

    /home/bil/test/snd-9/ script
    hiho
    :a-test
    32
    :(exit)
    /home/bil/test/snd-9/ 

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":

SchemeRubyForth
#!/home/bil/snd-9/snd -l
!#
(open-sound "oboe.snd")
(scale-by 2.0)
(save-sound-as "test.snd")
(exit)
#!/home/bil/snd-9/snd -batch
open_sound "oboe.snd"
scale_by 2.0
save_sound_as "test.snd"
exit
#! /usr/bin/env /home/bil/forth-snd/snd
"oboe.snd" open-sound drop
2.0 #f #f scale-by drop
"test.snd" save-sound-as
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 intervening arguments as it scans the startup arguments (see snd-test.scm).

#!/home/bil/test/snd-9/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 multichannel 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 Scheme; most of the *.scm (Scheme) files have corresponding *.rb (Ruby) files (written by Mike Scholz), so there's no penalty for using Ruby rather than Scheme. The only drawback is that the documentation uses Scheme. The differences between Scheme and Ruby, however, are not too severe. In Ruby, the following changes are made in the function names (as presented in Scheme): "-" becomes "_", "->" becomes "2", hooks and current_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 Scheme. Here's a 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 Scheme 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

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. 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 Scheme'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(?). My very informal timing tests indicate that Guile and Ruby run at the same speed. For Ruby as an Emacs subjob, see inf-snd.el.


Snd with Forth

Snd can be built to use Forth as its extension language. Forth is available at:

    http://sourceforge.net/projects/fth

An example initialization file (~/.snd_forth) is given in the initialization section. Mike Scholz (creator of the Forth library we use) has provided a bunch of Forth files along the lines of the Scheme and Ruby files described above -- see *.fs. See also Mike's very fine documentation in the fth package. Here's an example from his fth.texi file showing how to define an instrument and use it in with-sound:

instrument: src-simp { start dur amp sr sr-env fname -- }
    :file fname make-readin { f }
    :input f input-fn :srate sr make-src { sr }
    :envelope sr-env :duration dur make-env { en }
    start dur run
        i                 \ sample position for outa
        sr en env #f src
        amp f*
        *output* outa drop
    loop
    f mus-close drop
;instrument
"/usr/gnu/sound/SFiles/fyow.snd" value fyow-snd
#( 0.0 0.0 50.0 1.0 100.0 0.0 ) value sr-env
0.0 1.5 0.5 0.2 sr-env array->list fyow-snd ' src-simp with-sound


Snd and LADSPA Plugins

  init-ladspa
  list-ladspa
  analyse-ladspa library plugin [also analyze-ladspa]
  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.


Snd and ALSA

(This section is a lightly edited copy of some notes Fernando sent me). The default ALSA device in Snd is "default". This virtual device tries to play sounds at any sampling rate, provide any number of input channels, handle data format conversions, and so on. Experts will probably want to use the "hw:0" device instead (this is the first hardware device; "hw:1" is the second, and so on), but in that case, ALSA provides only whatever sampling rates the hardware provides. To change the device, or the internal buffering amounts, you can either set up environment variables, or use the parallel Scheme/Ruby/Forth variables mus-alsa-*. The environment variables are:

MUS_ALSA_PLAYBACK_DEVICEname of playback device ("default")
MUS_ALSA_CAPTURE_DEVICEname of capture (recording) device ("default")
MUS_ALSA_DEVICEname of the playback and capture device ("sndlib")
MUS_ALSA_BUFFERSnumber of "periods" (buffers) used
MUS_ALSA_BUFFER_SIZEnumber of samples per channel per buffer

These can be set either in your shell initialization file (~/.cshrc for tcsh), or in the shell:

    setenv MUS_ALSA_DEVICE "hw:0"   ; tcsh
    MUS_ALSA_DEVICE="hw:0"          ; bash

or run Snd with a temporary setting:

    MUS_ALSA_DEVICE="plughw:0" snd somefile.wav

The parallel variables (for ~/.snd and friends, or at any time in the listener), are:

mus-alsa-playback-devicename of playback device ("default")
mus-alsa-capture-devicename of capture (recording) device ("default")
mus-alsa-devicename of the playback and capture device ("sndlib")
mus-alsa-buffersnumber of "periods" (buffers) used
mus-alsa-buffer-sizenumber of samples per channel per buffer

So, to use the "plughw" device, we could:

    (set! (mus-alsa-device) "plughw:0")

Similarly, if you know which device you want to record from (say your arecord command is "arecord -D hw:0.2"), you can tell Snd's recorder to use that device via:

    setenv MUS_ALSA_CAPTURE_DEVICE "hw:0.2"

or

    (set! (mus-alsa-capture-device) "hw:0.2")

It's also possible to define your own device, apparently, but that part of ALSA is beyond my ken.


Driving Snd remotely

It is possible to send Snd arbitrary Scheme, Ruby, or Forth 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. These are used automatically by the spectrogram display; the colormap is set by either the View:Colors dialog or colormap, and the orientation is set by either the View:Orientation dialog or the various spectro-* variables.

gl

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)) ; the GL 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. To turn these graphs into Postscript files, include the --with-gl2ps switch at configuration time, and use the gl-graph->ps function.


GL spectrogram
now.snd GL spectrogram


related documentation: snd.html extsnd.html sndscm.html clm.html sndlib.html libxm.html index.html