Nearly everything in Snd can be set in an initialization file, loaded at any time from a saved state file, specified via inter-process communciation from any other program, invoked via M-x in the minibuffer, imbedded in a keyboard macro, or dealt with from the lisp listener panel. The syntax used is lisp; if the Guile library is loaded, the underlying language is actually Scheme, these entities are fully incorporated into lisp, and all of them can be used in arbitrarily complicated functions. I've tried to bring out to lisp nearly every portion of Snd, both the signal-processing functions, and much of the user interface. You can, for example, add your own menu choices, editing operations, or graphing alternatives. These extensions can be loaded at any time. If the listener is active (whether its pane is open or not) any forms typed to the M-X prompt will be copied to the listener, providing a history of the interactions. To activate the listener without opening it (to conserve screen space or whatever), use the function activate-listener.
Currently, if the listener is active, and some sound is selected, any characters typed while in the sound graph which it can't handle are passed to the listener; to exit the listener without using the mouse, type C-g.
In Guile versions 1.3.4 or later, remember to
(use-modules (ice-9 debug))
if you
want to get backtrace information from the error handler.
In the following, optional arguments are in italics, functions available only if Guile is loaded are bold face. Channels are numbered from 0. If a channel argument is omitted, the currently active channel is used, if any; otherwise channel 0. Each sound has an associated "index" used to refer to it in all the functions. This somewhat arbitrary number is more or less related to the sound's position in the display of sounds. If the index is omitted, the currently active sound is used; if none, the top sound (or leftmost if you're using the horizontal panes).
Sndlib (see sndlib.html for a complete list): next-sound-file aiff-sound-file riff-sound-file nist-sound-file raw-sound-file ircam-sound-file snd-16-linear snd-8-mulaw snd-8-linear snd-32-linear-little-endian snd-32-linear snd-8-alaw snd-8-unsigned snd-32-float-little-endian snd-64-double snd-24-linear snd-32-float snd-16-linear-little-endian FFT style (the Transform Options Display choice): normal-fft sonogram spectrogram Transform type: fourier-transform wavelet-transform hankel-transform chebyshev-transform legendre-transform autocorrelation walsh-transform hadamard-transform FFT Window type: rectangular-window hanning-window welch-window parzen-window bartlett-window hamming-window blackman2-window blackman3-window blackman4-window exponential-window riemann-window kaiser-window cauchy-window poisson-window gaussian-window tukey-window Zoom Focus style: focus-left focus-right focus-active focus-middle X-axis Label: x-in-seconds x-in-samples x-to-one Speed Control style: speed-as-float speed-as-ratio speed-as-semitone Channel Combination style; channels-separate channels-combined channels-superimposed Envelope Editor target: amplitude-env spectrum-env srate-env Graph Line style: graph-lines graph-dots graph-filled graph-lollipops graph-dots-and-lines Keyboard action choices: cursor-in-view cursor-on-left cursor-on-right cursor-in-middle cursor-update-display cursor-no-action cursor-claim-selection keyboard-no-action
These variables are accessed as though each were a function of no arguments, and set using a function with "set-" prepended to the variable name. For example, auto-resize's current value can be accessed via (auto-resize), and set to a new value via (set-auto-resize #t). (I can't use normal Scheme variables here because set! would not affect the user-interface that reflects the variable; I need the "set-" forms anyway, so making everything a function makes for less confusion, I hope). Many of the variables are switches that can take either the C-style boolean values 0 and 1, or the Scheme-style #f and #t (the variables reside in C, but Scheme values are translated to C values automatically).
ask-before-overwrite #f (Save-as): ask before overwriting an existing file audio-output-device sndlib-default-device Audio output device (for the play button) auto-resize #t should Snd window resize upon open/close (see AutoResize) auto-update #f should Snd update a file automatically. basic-color ivory2 main Snd color. channel-style channels-separate The default state of the 'unite' button in multi-channel files. Other values are channels-combined and channels-superimposed. color-cutoff 0.003 In spectra, sets the lowest data value that will be colored. color-inverted #t The 'invert' button in the color dialog, negated (hunh?!). color-scale 0.5 The darkness setting in the color dialog, divided by 100. colormap 0 Colormap choice for various displays (see the Color Editor). This should be an integer between -1 and 15. The maps (from 0 to 15) are: gray, hsv, hot, cool, bone, copper, pink, jet, prism, autumn, winter, spring, summer, colorcube, flag, and lines. -1 means black and white. corruption-time 60 Time (seconds) between background checks for changed file on disk (see auto-update). If less than 0.0, this background process is turned off. cursor-color red cursor color. dac-size 256 Audio output buffer size (not always meaningful). data-color black color of data in unselected graph. default-amp 1.0 These 'default' settings refer to the value given the control panel widgets when a sound is opened. default-amp is the amplitude setting. See amp). default-contrast 0.0 Initial contrast value. default-contrast-amp 1.0 Initial contrast scaler (see contrast-amp). default-contrasting #f Initial contrast button value (contrasting). default-expand 1.0 Initial expand value (expand). default-expand-hop 0.05 Initial expand hop value (expand-hop). default-expand-length 0.15 Initial expand segment length (expand-length). default-expand-ramp 0.4 Initial expand ramp length (expand-ramp). default-expanding #f Initial value of expand button (expanding). default-filter-order 20 Initial control panel filter order (filter-order). default-filtering #f Initial filter button value (filtering). default-output-type next-sound-file The default header type when a new or temporary file is created (defaultOutputType resource). default-reverb-feedback 1.09 Initial reverb feedback coefficient (reverb-feedback). default-reverb-length 1.0 Initial reverb delay length scaler (reverb-length). This scales the length of all the delay lines used in the reverberator, so higher numbers give longer initial delays and a more cavernous effect. In the control panel it is the scaler labelled "len:". default-reverb-lowpass 0.7 Initial reverb lowpass coefficient (reverb-lowpass). default-reverb-scale 0.0 Initial value of the reverb volume scaler (reverb-scale). default-reverbing #f Initial reverb button value (reverbing). default-speed 1.0 Initial speed (srate) scaler value (speed). dot-size 1 Size in pixels of dots when graphing with dots (see also examp.scm auto-dot function). edit-history-width 1 width (pixels) of edit history portion of channel pane. enved-base 1.0 Envelope editor exponential base value enved-clipping #f Envelope editor 'clip' button If clipping, the motion of the mouse is restricted to the current graph bounds. enved-dBing #f Envelope editor 'dB' button enved-exping #f Envelope editor 'exp' and 'lin' buttons If exping, the connecting segments use exponential curves rather than straight lines. enved-power 3.0 Envelope editor base scale range (9.0^power). enved-target amplitude-env Determines how the envelope is applied to the current data This chooses one of the 'amp', 'flt', and 'src' buttons in the Envelope editor. The other (named constant) choices are srate-env and spectrum-env. enved-waveform-color blue color of waveform displayed in envelope editor. enved-waving #f Envelope editor 'wave' button The wave shown is the time domain display, even when filtering. eps-file "snd.eps" Name of the Postscript file produced by the File Print option. See also the epsFile resource. fft-beta 0.0 The fft data window parameter, if relevant. If any of the FFT variables is set to a new value, call update-ffts to see the effect. fft-log-frequency #f If #t, the spectrum frequency axis is logarithmic, not linear. fft-log-magnitude #f If #t, the spectrum magnitude axis is in decibels. fft-size 256 FFT size. fft-style normal-fft The other choices are sonogram and spectrogram. fft-window blackman2-window FFT data window. See the long list above for other choices. filter-env-order 40 The order of the Envelope editor's FIR filter. filter-waveform-color blue color of control panel filter waveform. fit-data-on-open #f If #t, the initial time-domain display of a sound shows its entire duration, with the Y-axis set to show its maxamp. graph-color white background color of unselected graph. graph-cursor XC_crosshair (34) cursor displayed following mouse in graph The cursors are declared in /usr/X11R6/include/X11/cursorfont.h or some such file. graph-style graph-lines The other choices are graph-dots, graph-filled etc. highlight-color ivory1 highlighting color. initial-x0 0.0 Initial time domain window left bound (seconds). initial-x1 0.1 Same, but on the right; actual x1 is the lesser of this and the sound's duration initial-y0 -1.0 Initial window y axis minimum. initial-y1 1.0 Same, but maximum. line-size 128 Number of samples considered to be a 'line' (C-n and C-p commands). listener-color aliceblue background color of lisp listener. listener-prompt ">" lisp listener prompt (a one-character string). mark-color red color of mark indicator. max-fft-peaks 100 Max number of fft peaks reported. max-fft-size 0 In convolve-with, max size of in-memory fft (0 = limited only by malloc). max-regions 16 Size of region stack. memo-sound #f When a sound file is opened, Snd looks for a file with the same name but with an appended ".scm" extension. If such a file is found, it is loaded automatically. The variable memo-sound is set to the newly opened sound's index. This supports the "snd-memo" feature in CLM, but can be used independently of CLM to store marks, selections, or whatever that you want associated with a particular sound. min-dB -60.0 Sets the minimum dB value displayed in various graphs. mix-amp-scaler 1.0 Multiplier on amp scales in mix consoles (see mix consoles). mix-color lightgreen color of mixer consoles. mix-focus-color green color of selected mix consoles. mix-speed-scaler 1.0 Multiplier on speed scales in mix consoles (mix consoles). mix-tempo-scaler 1.0 Multiplier on tempo scales in group consoles. mix-waveform-color darkgray color of mix waveform. mix-waveform-height 20 Max height (pixels) of mix waveforms (see show-mix-waveforms). mixer-group-max-out-chans 4 Maximum number of output scalers in mix console. mixer-groups 6 Number of mixer groups available. mixer-save-state-file ".snd-mixer" Name of file for save mixer state. movies #t If #t, the mix graphs are updated constantly as the mouse drags the mix console. normalize-fft #t If #t, spectral data is normalized to 1.0 before display. If #f, you get the raw data values, which can reflect amplitude changes. Snd tries to choose a y axis limit that makes successive displays move smoothly. normalize-on-open #t When a new sound is added to the Snd window, the resultant set of graphs can start to dangle off the bottom or end of the screen. If normalize-on-open is #t, Snd tries to do something reasonable in this case. position-color ivory3 position slider color prefix-arg 0 This is the keyboard C-u style argument (named current-prefix-arg in Emacs). previous-files-sort 0 Sort choice in files dialog (0=unsorted, 1=name, etc). print-length 12 In the listener, print-out of lists and vectors that are longer than print-length is truncated. pushed-button-color lightsteelblue1 color of pushed button. raw-chans 1 The "raw-" variables refer to sound data interpretation choices made when a sound is opened that appears to be headerless. raw-chans sets the default number of channels. raw-format 16-linear The default data format in a headerless sound. Other possibilities are given above, or you can use the Sndlib macros directly. raw-srate 44100 The default headerless sound sampling rate. raw-type next-sound-file The default headerless file header type (for subsequent saves and so on). Snd only writes NeXT/Sun, AIFF, RIFF ('wave'), IRCAM, or raw files; the corresponding Sndlib macros are given above. recorder-autoload #f The 'autoload' button in the recorder dialog. recorder-buffer-size 4096 The size of the recorder input buffer (there's a trade-off between responsiveness and clicks in some cases). recorder-file nil Default recorder output file name. recorder-in-format 16-bit linear Incoming data format for the recorder. It's not currently safe to mess with this. It defaults to the host byte order. recorder-max-duration 1000000.0 Recorder max output file length. recorder-out-chans 2 Recorder output file channels. recorder-out-format same as recorder-in-format recorder-srate 22050 Recorder sampling rate. recorder-trigger 0.0 Recorder auto-trigger value. reverb-decay 1.0 The length (seconds) of the reverberation after the sound has finished. The sound-local (control panel) version of this is reverb-length. save-state-on-exit #f If #t, Snd saves its current state in save-state-file. save-state-file "saved-snd.scm" The default saved state file name. selected-data-color black color of data in currently selected graph. selected-graph-color white background color of currently selected graph. selection-color lightsteelblue1 color of selected portion of graph. show-axes #t If #t, display x and y axes. This is the 'Show axes' View menu option. show-edit-history #f (Motif 1 only) If #t, include edit history window in channel pane. show-fft-peaks #f If #t, fft peak information is included in the fft display. (This is the 'peaks' button in the Transform options dialog). show-marks #t If #t, marks are displayed. This is the 'Show marks' View menu option. show-mix-consoles #t If #t, mix consoles are displayed. This is the 'Show consoles' View menu option. show-mix-waveforms #t If #t, mixer console displays the waveform of the sound being mixed. show-selection-transform #f If #t, display the transform of the current active selection, if any. show-usage-stats #f If #t, show approximate memory and disk space usage of current edit trees. show-y-zero #f If #t, the y=0 axis is displayed. This is the 'Show Y=0' View menu option. sinc-width 10 Width (in samples) of the sampling rate conversion sinc interpolation. The higher this number, the better the src low-pass filter, but the slower src runs. If you use too low a setting, you can sometimes hear high frequency "whistles" leaking through. To hear these on purpose, make a sine wave at (say) 55 Hz, then (src-sound '(0 3 1 1)) with sinc-width at 4. spectro-cutoff 1.0 The amount of the frequency domain to include in the spectrum display. This number changes as you drag the frequency axis, for example. This is the slider labelled '% of spectrum' in the View Orientation dialog. spectro-hop 4 The distance (samples) moved between successive spectrogram traces. This is the slider labelled 'hop' in the Orientation dialog. spectro-x-angle 90.0 Default spectrogram x-axis viewing angle. spectro-x-scale 1.0 Default scaler (stretch) along the spectrogram x axis. spectro-y-angle 0.0 Same for y-axis. spectro-y-scale 1.0 Same for y-axis. spectro-z-angle -2.0 Same for z-axis spectro-z-scale 0.1 Same for z-axis. For all of these variables, the easiest way to see what they refer to is to run the Orientation dialog with a spectrogram displayed. speed-style speed-as-float In the control panel, the 'speed' control can be interpreted as a continuum (speed-as-float), as a just-intonation ratio of relatively small integers (speed-as-ratio) or as a step in a microtonal scale (speed-as-semitone). The default is 12 tones to the octave. See the Speed style Options menu option. speed-tones 12 The number of tones per octave in the speed-as-semitone speed style. temp-dir nil Name of directory for temporary files. nil usually means "/var/tmp". text-focus-color white color of text field when it has focus. transform-type fourier-transform The spectrum transform type. Other types are given above. trap-segfault #t If #t, try to catch segfaults and continue anyway. use-raw-defaults #f If #t, the "raw-" variables' values are used automatically when a headerless file is encountered. If #f, Snd fires up the raw file dialog to find out how to interpret the data. verbose-cursor #f If #t, the cursor's position and other information is constantly displayed in the minibuffer. This is the View menu's Verbose cursor option. vu-font nil The "vu-" variables refer to the VU meters in the recorder. vu-font is the font used to label the meters. It is normally "courier". vu-font-size 1.0 This sets the recorder VU meter label font size. vu-size 1.0 This sets the overall size of the recorder VU meters. wavelet-type 0 If transform-type is wavelet-transform, wavelet-type selects which wavelet is used. The list of available wavelets is in the Transform Dialog. There are currently 20 choices, so this variable goes from 0 to 19. wavo #f If #t, the time domain waveform is displayed as a 'wavogram'. wavo-hop 3 This sets the distance upward between wavogram traces; that is, the smaller this number, the more traces can be displayed. wavo-trace 64 This sets the length (samples) of each wavogram trace. window-height 0 The current Snd window height in pixels. window-width 0 The current Snd window width in pixels. window-x -1 The current Snd window left side in pixels. window-y -1 The current Snd window upper side in pixels (X numbering starts at 0 at the top). x-axis-style x-in-seconds The x axis labelling of the time domain waveform can be in seconds (x-in-seconds), in samples (x-in-samples), or expressed as a percentage of the overall duration (useful in envelope definitions). The latter is x-to-one. This is the View menu 'X-axis units' option. xmax 0.0 Sets the x axis maximum (truncates the display if the sound is longer). That is, you can display anything below xmax normally, but Snd refuses to go past that point. Not sure why you'd want this... The default of 0.0 turns off this feature. xmin 0.0 Sets the x axis minimum. ymax 1.0 Sets the y axis maximum (useful to narrow the range of slider action). ymin 1.0 Sets the y axis minimum. zero-pad 0 fft zero pad size as a multiple of the fft size; (set-zero-pad 1) gives you half data, half zeros. zoom-color ivory4 zoom slider color. zoom-focus-style focus-active This determines what a zoom action focuses (centers) on. See Zoom options.
These functions give lisp access to most of Snd's data structures and functions. In the argument lists below, snd as an argument refers to the sound's index, and defaults to the currently selected sound. Similarly, chn is the channel number, starting from 0, and defaults to the currently selected channel. So if there's only one sound active, and it has only one channel, (cursor) (cursor 0), and (cursor 0 0) all refer to the same thing.
abort () drop into gdb abort? () check for C-g to interrupt on-going computation (and let other UI events through). activate-listener () make listener active, even if not open. active-sounds () return number of currently active (displayed) sounds. add-mark (sample snd chn) add mark at sample, returning mark id. add-sound-file-extension (ext) add ext to the list of sound file extensions. add-to-main-menu (menu-label) add new top-level menu named menu-label, return menu index. (add-to-main-menu "Tools") -> 5 see examples in examp.scm. add-to-menu (top-menu menu-label callback) add menu menu-label to top-level menu whose index is top-menu with the lisp callback string callback. The built-in Snd menus are numbered from 0 ('File') to 4 ('Help'). see also remove-from-menu. (add-to-menu 5 "Denoise" "(report-in-minibuffer \"denoise\")") amp (snd) return current amp (control panel slider) value. append-to-minibuffer (msg snd) append msg to whatever is in snd's minibuffer. audio-outputs (speakers headphones line-out) (Sun only) set which output devices are active. autocorrelate (data) return (in place) autocorrelation of data backward-graph (count) move back (up or left) count graphs (C-x C-o). backward-mark (count) move back count marks (C-j). backward-mix (count) move back count mix consoles (C-x C-j). backward-sample (count) move back count samples (C-b), return new cursor position. bind-key (key state code ignore-prefix) Cause key (an integer) with modifiers state to evaluate code. as in the hook functions: (bind-key (char->integer #\a) 4 "(snd-print \"hi\"))") The modifier state is a combination of shift: 1, control: 4, meta: 8, so this call causes C-a to print "hi" in the lisp listener. The value returned should be one of the cursor choices telling Snd what action (if any) to take after evaluating code. If ignore-prefix is #t, Snd does not repeat the key based on the prefix argument (C-u) -- in this case, the code can examine prefix-arg if desired. See examp.scm for several examples. (code can be a scheme function if Guile is loaded). bomb (snd on) display bomb icon in snd's minibuffer. If on (default #t) is #f, the bomb is erased. call-apply (snd) equivalent to pushing snd's 'apply' button. This can be used in conjunction with the various control panel variables: (define expnd (lambda (amt) (set-expanding #t) (set-expand amt) (call-apply))). call-plug (plug) invoke the plug as an editing operation. call-plug-selection (plug) invoke the plug as an editing operation over current selection. change-menu-label (top-menu old-label new-label) channels (snd) return number of channels in snd. chans (snd) same as channels (the forgetful programmer's friend). clear-audio-inputs() in Linux/OSS, try to reduce soundcard background racket. clm? () return 1 if clm is loaded. close-sound (snd) close snd (same as File menu Close). close-sound-file (fd bytes) close file updating header to report bytes bytes of data. (See open-sound-file). color? (obj) return #t if obj is a color object (see make-color). color-dialog () fire up the Color dialog. comment (snd) return snd's comment, if any. contrast (snd) return current contrast (control panel slider) value. contrast-amp (snd) return snd's contrast-amp (control panel variable). contrasting (snd) return #t if snd has contrast turned on (control panel) convolve-arrays (rl1 rl2) convolve vectors rl1 with rl2. Result returned in rl1. rl1 should be large enough to hold the full convolution result. As a special dispensation for forgetful users, if rl1 is a file name and rl2 is not a vector, convolve-with is called instead. convolve-with (file amp snd chn) convolve snd's channel chn (or the currently sync'd data) with the data in the sound file file. If your machine is short on memory, set max-fft-size first. amp is the resultant peak amplitude (leave amp unset, or set it to #f to get the unnormalized result). Convolve-with in conjunction with mix can provide high-quality reverb: (define conrev (lambda (impulse amp) (convolve-with impulse amp) (save-sound-as "reverb.snd") ;this extra step so mix console scalers set reverb amount (revert-sound) (mix "reverb.snd"))) convolve-selection-with (file amp) convolve the current selection with file. count-matches (expr sample snd chn) return how many samples satisfy the expression expr. expr can be either a C expression (a string), or a Scheme function of one argument. For example, (count-matches "y>.1") returns the number of samples greater than .1. sample determines where to start the search. The same thing in Scheme is: (count-matches (lambda (y) (> (or y 0.0) .1))) cursor (snd chn) return cursor location (samples) of channel chn of snd. cursor-follows-play (snd) return #t if cursor is following along in the sound as it plays. cut () cut the current selection (a no-op if no active selection). data-format (snd) return snd's data format (sndlib). data-location (snd) return snd's data location (bytes). delete-mark (id snd chn) delete mark id in snd's channel chn (- C-m). delete-marks (snd chn) delete all marks in snd's channel chn delete-region (reg) delete region number reg (which defaults to 0). This removes the region from the region stack; it doesn't edit the corresponding file(s). delete-sample (samp snd chn) delete sample samp in snd's channel chn. delete-samples (samp samps snd chn) delete samps samples starting at sample samp. describe-plug (plug) describe plug dismiss-all-dialogs () deactivate all dialogs. edit-header-dialog() fire up Edit Header dialog. edits (snd chn) return a vector with number of undo-able edits and redo-able edits. env-sound (envelope samp samps env-base snd chn) apply (in amplitude) envelope to snd's channel chn starting at sample samp for samps samples with connecting segments based on env-base. env-base defaults to 1.0 (line segments). samp defaults to 0. samps defaults to the full duration. envelope is a list or vector containing the breakpoint values (as in CLM). (env-sound '(0 0 1 1 2 0)) env-selection (envelope env-base snd chn) apply envelope to the currently selected portion of snd's channel chn. enved-dialog () fire up the Envelope editor dialog. exit () exit Snd. expand (snd) return current expansion amount (control panel). expand-hop (snd) return snd's expansion hop amount (seconds). expand-length (snd) return snd's expansion segment length (seconds). expand-ramp (snd) return snd's expansion ramp amount (between 0 and .5). This affects the smoothness of the grain overlaps -- .001 is a rattling effect. expanding (snd) return #t if snd's expand button is on. fft (rl im sgn) perform an FFT on rl and im (the real and imaginary parts of the input data. sgn is 1 for an FFT, -1 for an inverse FFT; it defaults to 1. ffting (snd chn) return #t if snd's channel chn is displaying a spectrum (the 'f' button). file-dialog () fire up the list of current and previous files (not the file browser). file-name (snd) snd's complete file name. filter-env (snd) snd's filter envelope (control panel). filter-dBing (snd) snd's filter dB button state (control panel). filter-order (snd) snd's filter order (control panel). filter-sound (env order snd chn) apply an FIR filter of order order and frequency response env to snd's channel chn. filtering (snd) return #t if snd is filtering (control panel filter button). filter-selection (env order) apply an FIR filter of order order and frequency response env to the current selection. find (c-expr sample snd chn) find the sample that satisfies the C expression (a string) c-expr. For example, (find "y>.1") looks for a sample greater than .1, returning the sample number if one is found. sample determines where to start the search. c-expr can also be a Scheme function. find-mark (samp snd chn) return identifier of mark at sample samp or #f if none found. This identifier is used in calls such as mark-sample. Since marks can move around during editing, a unique 'tag' is needed to refer to a particular mark. samp can also be a string; in this case find-mark looks for a mark of that name. find-sound (filename) returns the index of filename (used as snd throughout). return #f if no sound is found that matches filename. finish-progress-report (snd) see progress-report. forward-graph (count) move forward (down or right) count graphs (C-x C-o). forward-mark (count) move forward count marks (C-j). forward-mix (count) move forward count mix consoles (C-x C-j). forward-sample (count) move forward count samples (C-f), return new cursor position. frames (snd chn) return chn's current length in samples. graph (data xlabel x0 x1 y0 y1 snd chn) Display a graph of data in a separate display per channel. The x axis is labelled xlabel, the x axis units go from x0 to x1 (default 0 to 1.0), the y axis goes from y0 to y1 (default fits the data), and the display is associated with channel chn in snd. data should be a vector. (graph #(0 .1 .2 .3 .4 .3 .2 .1 0) "roof") graphing (snd chn) return #t if graph data is being displayed. graph->ps () create Postscript description of current display (see eps-file). group-amp (group chan) return group's chan-th output amplitude. group-beg (group) return group's begin time. group-dialog () fire up the Groups browser. group-end (group) return group's end time. group-ok? (group) return #t if group is active (i.e. has a member mix). group-speed (group) return group's speed. group-tempo (group) return group's tempo. groups () return how many groups are currently active. guile? () return 1 if guile is loaded. If you're running on a system where several Snd's are running, some without Guile, and you want to include Guile-isms in your initialization file, precede such code with (if (guile?)...) header-type (snd) return (type . name) help-dialog (subject help) fire up the help dialog with title subject and body help. (help-dialog "xyzzy" "are we having fun?") hide-listener () close the lisp listener pane. in (ms code) ms milliseconds from now, evaluate code, a string containing Scheme code, or a Scheme function (see examp.scm). The auto-save implementation in examp.scm uses in. insert-sound (file in_chan snd chn) insert channel in_chan of file at the cursor in snd's channel chn. insert-region (beg reg snd chn) insert region reg at sample beg in snd's channel chn. insert-sample (samp value snd chn) insert sample value at sample samp in snd's channel chn. insert-samples (samp samps data snd chn) insert samps samples of data starting at sample samp in snd's channel chn. data can be a filename; Snd assumes any such file is temporary; it will be deleted when no longer needed. key (key state) execute the keyboard command key with modifier keys state. shift: 1, control: 4, meta: 8 (see /usr/include/X11/X.h ControlMask et al). left-sample (snd chn) return the position in samples of the left edge of the time domain waveform for snd's channel chn. list->vct (lst) return vct object with elements of list lst load-colormap (colors) use colors in colors (a vector) as current colormap. This is still kludgey, but the following shows how to use it: (load "rgb.scm") (define hi (make-vector 512)) ;use 64 if not using big colormaps (do ((i 0 (+ i 4))) ((>= i 512)) (vector-set! hi i red) (vector-set! hi (+ i 1) blue) (vector-set! hi (+ i 2) green) (vector-set! hi (+ i 3) black)) (load-colormap hi) make-color (r g b) return a color value using the red/green/blue values r/g/b -- each of these is a float running from 0.0 to 1.0. (make-color 1.0 0.0 0.0) returns red. make-region (beg end snd chn) create a new region spanning samples beg to end in snd's channel chn. make-vct (len) create vct struct of size len. map-across-all-chans (func start end edname) apply func to all open channels in parallel (see Scanning Data). map-across-chans (func start end edname) apply func to currently syncd channels in parallel map-across-sound-chans (func start end edname snd) apply func to sound's channels in parallel map-all-chans (func start end edname) apply func to all open channels map-chan (func start end edname snd chn) apply func to samples in current channel (see Scanning Data). map-chans (func start end edname) apply func to currently syncd channels map-sound-chans (func start end edname snd) apply func to current sound's channels mark-name (id snd chn) return name of mark id. mark-sample (id snd chn) return position of mark id. marks (snd chn) return number of marks in snd's channel chn. max-sounds () return current size of sound array (grows as required, may contain holes). maxamp (snd chn) return max amp of snd's channel chn. mix (file samp in_chan snd chn) mix file's channel in_chan starting at samp in snd's channel chn. if only the file argument is given, this is equivalent to the File menu's Mix option. mix-amp (mix chan) return amplitude of mix's channel chan. mix-anchor (mix) return anchor position (within the mix) of mix. mix-groups (mix) return a bit-wise indication of the groups mix is participating in. That is, group 0 is bit 0, etc. If mix is in groups 3 and 4, this returns 24. mix-length (mix) return length in samples of mix. mix-position (mix) return position (sample number) of mix. mix-region (samp scaler reg snd chn) Mix in region reg at sample samp (defaulting to the cursor sample), scaled by scaler (defaults to 1.0) in snd's channel chn. mix-speed (mix) return speed of mix. mix-state (mix) return console state of mix (0=open, 1=title bar, 2=name[0]). new-sound (name header-type data-format srate chans) create a new (empty) sound named name. If the type and other arguments are not specified, the raw file dialog is posted to get the needed values which default to the current raw-type and related settings. normalize-view () normalize Snd display as in View menu Normalize option. ok? (snd) return #t if snd (an index) is active. open-raw-sound (name chans srate format) open name as a raw (no header) sound in the layout specified. open-sound (name) open name as in File menu Open option. open-sound-file (name chans srate comment) Open (create) a sound file name (defaults to "test.snd" or "test.wav"). It is assumed that the data will be floats in the native format (written by the caller interleaving channels), and that the file will be closed by close-sound-file. One simple way to write the data is to call vct->sound-file. open-alternate-sound(name) close the currently selected file, if any, and open name. orientation-dialog() fire up the Orientation dialog. override-data-format(format snd)force data format (ignore header) override-data-location(loc snd) force data location (ignore header) override-data-size(samps snd) force data size (ignore header) peaks (file snd chn) display fft peak information. If file is not null, write the information to that file, else post it in a help window (where it can be selected and pasted elsewhere). This function follows the state of the various 'sync' buttons, given snd and chn. play (samp snd chn) play snd's channel chn starting from sample samp. play-and-wait (samp snd chn) play snd's channel chn starting from sample samp and wait for it to finish. play-region (reg) play region reg. preload-directory (dir) preload sound files from directory dir (see -p). preload-file (file) preload file (see View menu's View Files option). progress-report (pct name current-channel channels snd) The functions start-progress-report, progress-report, and finish-progress-report handle the animated hour-glass icon used to amuse the idle user while some long computation is allegedly in progress. The pct argument is a float between 0.0 and 1.0 which indicates how far along we are in the computation (there are actually only 15 or 20 separate icons, so there's no point in calling this more often than that). start-progress-report posts the initial icon, and finish-progress-report removes it. If the icons are not available, a message is posted in snd's minibuffer using name and so on to identify itself. protect-region (reg protect) protect/unprotect region reg from deletion in the region browser. read-only (snd) return #t if snd is read-only, #f otherwise. recorder-dialog () fire up recorder window. recorder-gain (gain) return recorder input (soundcard-audio) gain gain. recorder-in-amp (in out) return recorder input channel in to output channel out amplitude. recorder-out-amp (out) return recorder file output channel out amplitude. redo (edits snd chn) redo edits edits (default is 1) in snd's channel chn. region-chans (reg) return number of channels in region reg. region-dialog () fire up region browser (a no-op if no regions). region-length (reg) return number of samples (per channel) in region reg. region-maxamp (reg) return maximum amplitude of region reg. region-sample (samp reg chn) return value of sample samp in region reg in snd's channel chn. region-samples (samp samps reg chn) return vector of samps samples starting at samp in region reg's channel chn. region-samples->vct (samp samps reg chn) return vct struct of samps samples starting at samp in region reg's channel chn. region-srate (reg) return original (nominal) sampling rate of region reg. regions () return number of regions in the region stack. remove-from-menu (top-menu menu-label) remove menu menu-label from the top top-level menu whose index is top-menu. report-in-minibuffer (msg snd) post msg in snd's minibuffer. restore-control-panel (snd) same as pushing the control panel 'r' button. reverb-feedback (snd) return snd's reverb feedback coefficient. reverb-length (snd) return reverb delay line length scaler (control panel). reverb-lowpass (snd) return reverb low pass filter coefficient. reverb-scale (snd) return reverb amount (control panel). reverbing (snd) return #t if snd's reverb button is on. reverse-selection () reverse data delineated by current selection. reverse-sound (snd chn) reverse data. revert-sound (snd) revert snd to saved state (undo all edits). right-sample (snd chn) return position (samples) of right edge of time domain waveform. sample (samp snd chn) return value of sample samp in snd's channel chn. samples (samp samps snd chn) return vector of samps samples starting at samp in snd's channel chn. samp defaults to 0. samps defaults to frames - samp. samples->vct (samp samps snd chn) return vct struct with same data as in samples call above. save-control-panel(snd) same as pushing the control panel 's' button. save-edit-history (filename snd chn) save current edit list(s) in filename. If chn is omitted, all snd's channels are saved; if snd is omitted, all edit list are saved. If the underlying files are not subsequently changed, you can load this file to restore the current edit list state. Returns #t if successful (file opened ok); #f is something went wrong. save-envelopes (filename) save envelope editor contents in filename. save-macros () save keyboard macros in Snd's init file (.snd). save-marks (snd) save snd's marks, writing a file <name>.marks. save-options (filename) save options in filename. save-region (reg filename format) save region reg in filename in data format format (default snd-16-linear). save-selection (file header-type data-format srate comment) save the currently selected data in file. save-sound (snd) save snd; same as File menu's Save option. save-sound-as (filename snd header-type data-format srate) save snd as filename (same as File Save as option). save-state (filename) save current state of Snd in filename. scale-by (scalers snd chn) scale amplitude of snd by scalers. Unlike most of these functions, scale-by follows the 'sync' buttons and affects all currently sync'd channels. scalers can be either a float or a vector of floats. In the latter case, the values are used one by one, applying each as scale-by moves through the channels. If 'sync' is off, channel chn is scaled (defaults to the currently selected channel). scale-selection-by(scalers) scale the current selection by scalers which can be either a float, or a vector of floats. scale-selection-to(scalers) normalize the current selection to scalers which can be either a float, or a vector of floats. scale-to (scalers snd chn) normalize snd to scalers. scan-across-all-chans (func start end) apply func to all open channels in parallel (see Scanning Data). scan-across-chans (func start end) apply func to currently syncd channels in parallel scan-across-sound-chans (func start end snd) apply func to sound's channels in parallel scan-all-chans (func start end) apply func to all open channels scan-chan (func start end snd chn) apply func to samples in current channel (see Scanning Data). scan-chans (func start end) apply func to currently syncd channels scan-sound-chans (func start end snd) apply func to current sound's channels select-all (snd chn) create a new region spanning all samples in snd's channel chn. select-channel (chn) select channel chn. select-region (reg) select region reg (i.e make it region 0). select-sound (snd) select sound snd. selected-channel (snd) return selected channel in snd. selected-sound () return selected sound (index). selection-beg () return selection begin sample number. selection-length () return selection frames. selection-member (snd chn) return #t if snd's chn is member of active selection. selection-to-temp (type format) write out selected data as a temp file (see external programs). selection-to-temps(type format) write out selected data as temp files (see external programs). ;; The notation "(default #t)" below means the default value of the boolean argument ;; is #t; normally the default value of the associated boolean itself is #f. This ;; means that the command (set-contrasting) turns on contrasting, rather than ;; turning it off (doesn't that make more sense?). set-amp (amp snd) set snd's amp to amp (control panel). set-contrast (contrast snd) set snd's contrast amount to contrast (control panel). set-contrast-amp (contrast-amp snd) set snd's contrast-amp (control panel). set-contrasting (contrasting snd) set snd's contrast button to contrasting (default #t). set-cursor (samp snd chn) place snd's channel chn's cursor at samp. set-cursor-follows-play (cursor-follows snd) if #t, the cursor runs alongside the waveform as the sound if played (default #t). set-expand (expand-amount snd) set snd's expansion amount to expand-amount. set-expand-hop (expand-hop snd) set snd's expand-hop to expand-hop. This is the amount the expander moves forward in the output on each segment. If hop > length, you get a series of individual (isolated) grains. set-expand-length (expand-length snd) set snd's expand-length (segment length) to expand-length. This is the length (seconds) of each grain. set-expand-ramp (expand-ramp snd) set snd's expand-ramp (ramp time) to expand-ramp. set-expanding (contrasting snd) set snd's expand button (default #t). set-ffting (on snd chn) set snd's channel chn's 'f' button (default #t). set-filter-dBing (dB snd) set snd's filter dB button state. set-filter-order (filter-order snd) set snd's filter-order. set-filter-env (filter-env snd) set snd's filter frequency response envelope (a list). set-filtering (filtering snd) set snd's filter button (default #t). set-graphing (on snd chn) if #t (1), snd's channel chn's displays any lisp-generated graphics data (see graph-hook and graph). set-group-amp (group chan amp) set group's channel chan amplitude to amp. set-group-beg (group beg) set group's begin time to beg (moves all associated mixes). set-group-end (group end) set group's end time to end (moves all mixes to accomodate). set-group-speed (group speed) set group's speed to speed. set-group-tempo (group tempo) set group's tempo to tempo. set-just-sounds (just-sounds) set just-sounds button in file browser (default #t). set-left-sample (samp snd chn) set snd's chn's left sample (the window bound) to samp. set-mark-name (id name snd chn) set mark id's name to name (in snd's channel chn). set-mark-sample (id sample snd chn) set mark id's position to sample (in snd's channel chn). set-menu-sensitive(top-menu label sensitive) set-mix-amp (mix chan amp) set mix channel chan's amplitude to amp. set-mix-anchor (mix anchor) set mix's anchor to anchor. set-mix-groups (mix groups) set mix's associated groups (bit-wise) to groups. set-mix-length (mix length) set mix's length (samples) -- this can be dangerous! set-mix-position (mix samp) set mix's position (begin time in samples). set-mix-speed (mix speed) set mix's speed. set-mix-state (mix state) set mix'e title/console display state (0=open, 1=title, 2=name[0]). set-read-only (read-only snd) set snd's write-protection to be read-only (default #t). set-recorder-gain(gain amp) set recorder's hardware gain gain to amp. set-recorder-in-amp(in out amp) set recorder's input in to output out amp to amp. set-recorder-out-amp(out amp) set recorder's file output channel out to amp. set-reverb-feedback(feedback snd) set snd's reverb feedback coefficient to feedback. set-reverb-length(length snd) set snd's reverb delay line length scaler to length. set-reverb-lowpass(lowpass snd) set snd's lowpass coefficient to lowpass. set-reverb-scale(scale snd) set snd's reverb amount to scale. set-reverbing (on snd) set snd's reverb button (default #t). set-right-sample (samp snd chn) set snd's channel chn's right sample position to samp. set-sample (samp value snd chn) set snd's channel chn's sample samp to value. set-samples (samp samps data snd chn) set snd's channel chn's samples satrting from sample samp for samps samples to the values in data. (If samp is beyond the end of the file, the file is first zero-padded to reach it). data can be a filename; Snd assumes any such file is temporary; it will be deleted when no longer needed. set-showing-controls (showing snd) open or close the control panel (default #t). set-speed (speed snd) set snd's speed to speed (control panel). set-squelch-update(val snd chn) if val is not #f, turn off graphics updates else turn them on. set-syncing (syncing snd) set snd's 'sync' button (default 1). set-uniting (style snd) set snd's 'unite' button (default channels-combined). set-waving (on snd chn) set snd's channel chn's 'w' button (default #t). set-x-bounds (x0 x1 snd chn) set the display window x axis bounds to x0 and x1 (seconds). set-y-bounds (y0 y1 snd chn) set the display window y axis bounds to y0 and y1. If y1 is omitted, it is set from y0; if y0 is also omitted, the y axis bounds are set to reflect the channel's current max amp. short-file-name (snd) return the brief (no directory) form of snd's filename. showing-controls (snd) return #t if snd's control panel is open. show-listener () open the lisp listener pane. smooth-selection () apply a smoothing function to the current selection. smooth (beg num snd chn) apply a smoothing function to the indicated data. snd-print (str) display str in lisp listener, return str. (This is intended as a debugging aid -- there's still nothing like a lowly print statement). snd-spectrum (data window length linear) return spectrum of data (type vct) using fft-window win. length of data (and fft) is length. sound-to-temp (type format) write out sync'd edit state as a temp file (see external programs). sound-to-temps (type format) write out sync'd edit state as temp files (see external programs). speed (snd) return current speed (control panel). squelch-update (snd chn) return #t graphic updates are currently squelched (turned off). srate (snd) return snd's sampling rate. src-sound (num-or-env base) sampling rate conversion using 'warped sinc interpolation'. The argument num-or-env can be either a number or an envelope. In the latter case, base sets the segment base (default is 1.0 = linear). A value greater than 1.0 causes the sound to be transposed up. A value less than 0.0 causes the sound to be reversed. src-selection (num-or-env base) same as src but applied to current selection. start-progress-report (snd) see progress-report. stop-playing (snd) if snd is playing, stop. syncing (snd) return snd's 'sync' value (an integer, 0=off) temp-filenames (data) return vector of temp file names (see external programs). temp-to-selection (data name origin) read selected data from temp file (see external programs). temps-to-selection(data names origin) read selected data from temp files (see external programs). temp-to-sound (data name origin) read sync'd edit state from temp file (see external programs). temps-to-sound (data names origin) read sync'd edit state from temp files (see external programs). transform-dialog () fire up the Transform window (Option menu's Transform Options choice). transform-sample (bin slice snd chn) return the current value of the transform (if any) in bin and (if a sonogram or spectrogram) slice in snd's channel chn. transform-samples (snd chn) return the transform data currently in snd's channel chn. transform-samples->vct (snd chn) return vct struct with the transform data currently in snd's channel chn. unbind-key (key state) cause key with modifiers state to be a no-op. undo (edits snd chn) undo edits edits (default 1) in snd's channel chn. uniting (snd) 0 if channels are not superimposed or combined ('unite' button is off). update-sound () update the currently selected file. update-fft (snd chn) recalculate chn's fft. update-graph (snd chn) redisplay chn's graph(s). version () return Snd version (a string). vct? (vobj) return #t if vobj is a vct struct. vct-add! (vobj1 vobj2) vobj1[i] += vobj2[i], returns vobj1. vct-copy (obj) return a copy of obj. vct-fill! (vobj val) vobj[i] = val, returns vobj. vct-length (vobj) return length of data array in vobj. vct-multiply! (vobj1 vobj2) vobj1[i] *= vobj2[i], returns vobj1. vct-offset! (vobj val) vobj[i] += val, returns vobj. vct-ref (vobj pos) return value in vobj's data at location pos. vct-scale! (vobj scl) vobj[i] *= scl, returns vobj. vct-set! (vobj pos val) set vobj's data at location pos to val. vct->samples (samp samps data snd chn) synonym for set-samples. vct->sound-file (fd vobj vals) write vals floats from vobj to fd. view (filename) open filename read-only. waving (snd chn) return the state of snd's channel chn's 'w' button. x-bounds (snd chn) return (x0 . x1) -- current x axis time domain bounds in seconds. XmHTML? () return 1 if XmHTML is loaded. y-bounds (snd chn) return (y0 . y1) -- current y axis bounds. yes-or-no-p (ques) modal error dialog, returns #t if user clicks "ok", otherwise #f. defvar (var val) same as (define var val) except that the envelope editor keeps track of var thereafter and treats lists as envelopes. (defvar is a macro). I'm using defvar here rather than some more perspicuous name like def-envelope so that Snd and CLM can share envelope files. update-var (var-name) tell the envelope editor that some variable's value has changed (once the envelope dialog is running it won't otherwise notice these changes). var-name in this case is the name of the variable (a string). load (file) load file (containing lisp code -- this is Scheme's load function if Guile is loaded). string-length (str) length of str
The hooks provide a way to customize various situations that arise through user-interface manipulations. Each is a list of functions to be called or #f. See the Examples section and the file examp.scm for examples. If there is more than one function attached to a hook, some of the hooks effectively 'or' the functions together (marked or below), others 'progn' the list (marked progn); that is, to use less garish jargon, some run through the list of functions, and if any function returns #t, the hook immediately returns #t (ignoring the remaining functions), whereas in the other case, the result returned by the hook is the result of the last function in the list.
open-hook (filename) called each time a file is opened (before the actual open). (or) If it returns #t, the file is not opened. close-hook (snd) called each time a file is closed (before the close takes effect). (or) If it returns #t, the file is not closed. fft-hook (snd chn scaler) called just after an FFT (or spectrum) is calculated. (progn) graph-hook (snd chn y0 y1) called each time a graph is updated or redisplayed. (progn) If it returns #t, the display is not updated. exit-hook () called upon exit. (or) If it returns #t, Snd does not exit. start-hook (filename) called upon start-up. (or) If it returns #t, snd exits immediately. mouse-press-hook (snd chn button state x y) called upon mouse button press within lisp graph. (progn) mouse-release-hook (snd chn button state x y) called upon mouse button release within lisp graph. (progn) mouse-drag-hook (snd chn button state x y) called upon mouse motion (with button pressed) within lisp graph. (progn) key-press-hook (snd chn key state) called upon key press while mouse is in lisp graph. (or) If it returns #t, the key press is not passed on to the main handler. start-playing-hook (filename) called when a play request is triggered. (or) If it returns #t, snd does not play. stop-playing-hook (snd) called when a sound finishes playing. (or) mark-click-hook (id) called when a mark is clicked, return #t to squelch normal message. (progn) (add-hook! mark-click-hook (lambda (n) (help-dialog "Mark Help" (number->string (mark-sample n))) #t))
To add a function to a hook, use add-hook!. To remove it, use remove-hook!. To clear a hook, use reset-hook!. (These are functions supplied by Guile, and presumably someday they'll document them).
Sound files can be enormous, far larger than available memory, so vector access to sample data (as in set-samples) is not always very useful. (Each set-sample call is treated as a separate edit by Snd, but we would normally want the entire operation to be handled as one edit). In addition, the collection of available channels of data in the editor can present a complicated access problem. The 14 scanning and mapping functions provide what I hope is a straightforward answer to these problems. The "scan" functions do not change any data; they simply run through data presenting it to the caller. The "map" functions can change data, if they wish; the entire mapping call becomes one large editing operation from the editor's point of view. There are four ways to get at the editor's data: one channel, a sound's channels, all currently open channels, and all currently sync'd channels. There are two ways to march through this collection of channels: in series (that is, one channel at a time), and in parallel (all channels at once); the former is the default, and for the latter, I use the word "across". So map-chan maps a function over a single channel; map-chans affects the currently syncd channels (in series); map-across-chans affects the same set of channels, but they are presented to the caller's function as an array, each element of the array being a channel's sample at the current location in the map; map-all-chans affects all currently open channels; and finally, map-sound-chans affects all of a sound's channels, independent of the 'sync' button. The 'scan' functions behave similarly. In each case, an optional subsequence of the data can be requested via 'start' and 'end' points. If beg is #f, it defaults to 0; if end is #f, it defaults to the end of the channel.
map-across-all-chans (func start end edname) apply func to all open channels in parallel map-across-chans (func start end edname) apply func to currently syncd channels in parallel map-across-sound-chans (func start end edname snd) apply func to sound's channels in parallel map-all-chans (func start end edname) apply func to all open channels map-chan (func start end edname snd chn) apply func to samples in current channel map-chans (func start end edname) apply func to currently syncd channels map-sound-chans (func start end edname snd) apply func to current sound's channels scan-across-all-chans (func start end) apply func to all open channels in parallel scan-across-chans (func start end) apply func to currently syncd channels in parallel scan-across-sound-chans (func start end snd) apply func to sound's channels in parallel scan-all-chans (func start end) apply func to all open channels scan-chan (func start end snd chn) apply func to samples in current channel scan-chans (func start end) apply func to currently syncd channels scan-sound-chans (func start end snd) apply func to current sound's channels
In the case of the scanning operations, the function passed as the first argument takes either the current sample (when scanning in series), or two arguments, the current array of samples, and the array's length (when scanning in parallel). If the function returns something other than #f, the scan is stopped and (in the series case) a list is returned to the caller containing the non-#f value returned, the current sample position of the scan, the current channel number, and the current sound index; in the parallel case, the current sample position is returned. If the scan reaches the end of its data without ever getting a value other than #f from its function, it calls the function once more, passing #f as the first argument, and returns whatever the function returns. So, for example, the following call scans the current channel from sample 0 to the end looking for any sample greater than .1:
>(scan-chan (lambda (y) (> (or y 0.0) .1))) (#t 4423 0 0)
In this case, we found such a sample at position 4423 of the first channel of the sound whose index is 0. The '(or y 0.0)' form protects the '>' operation against the #f passed in the case of failure. Here's an example of scanning across all channels, returning the maximum sample value found:
(define data-max (lambda () (let ((maxval 0.0)) (lambda (data len) (if data (do ((i 0 (1+ i))) ((= i len) #f) (let ((curval (abs (vector-ref data i)))) (if (> curval maxval) (set! maxval curval)))) maxval))))) >(scan-across-all-chans (data-max)) 0.492675779232 >(define every-sample? (lambda (proc) (let ((baddy (scan-chan (lambda (y) (if y (not (proc y)) #f))))) (if baddy (set-cursor (cadr baddy))) (not baddy)))) >(every-sample? (lambda (y) (< y .5))) #tThe function 'data-max' returns a closure that includes the function we'll actually apply to the data (the inner lambda with the 'data' and 'len' arguments), and the variable that tracks the maxamp through the current call on 'scan-across-all-chans'. We include the extra layer of 'lambda' so that a subsequent call on '(data-max)' will start with a newly zeroed version of 'maxval'.
The mapping operations are slightly more complicated because they can edit the data. The fourth argument edname is the name of the editing operation that will be reported by the edit history mechanism. If none is given, it will default to the name of the calling map function (which has little to do with the actual edit). The other arguments to the mapping calls are the same as corresponding scanning calls. The function passed (and applied to the data) also takes the same arguments; its return value is interpreted differently however. The applied function can return #f, which means that the data passed in is deleted (replaced by nothing), or a number which replaces the current sample (in the parallel case this is an array of numbers), or #t which halts the mapping operation, leaving trailing samples unaffected, or a list, vct object, or vector of numbers (in the parallel case, an embedded array as an element of the outer array); in this case, the numbers are spliced into the edited version, effectively replacing the current sample with any number of samples. At the end of the map, the same function is called again with the first argument #f, and any returned values are spliced in. This sounds more complicated than it is! Basically, a map in series receives each sample and returns either #f (no corresponding output), a number (the new output), or a list of numbers; a map in parallel does the same for each sample in the array passed to it. If every value returned for a given channel is #f, the data is not edited. This makes it possible to run through all current channels in parallel, changing only one channel (or a subset of them).
>(map-chan (lambda (y) (if y (+ y .2) 'done))) done >(map-chan (lambda (y) (if y (cos y) )) #f #f "(cos y)") #<unspecified> >(map-chan (lambda (y) (if (and y (> y .1)) (list .1 .2 .3) y))) #f (define swap-channels (lambda () (if (= (channels) 2) (map-across-sound-chans (lambda (data chans) (if data (let ((chan0-sample (vector-ref data 0))) (vector-set! data 0 (vector-ref data 1)) (vector-set! data 1 chan0-sample) data) #f)) #f #f "swap-channels") (string-append (short-file-name) " is not stereo!"))))
The edit history may show multiple entries for a given map application; it may have to delete the old samples before inserting the new samples. This means that you may have to repeat 'undo' once or twice to get back to the state before the map operation. I'll fix this someday... Here's a slightly more involved example; we define a function that finds silences and replaces them with something:
(define map-silence (lambda (silence replacement) (let ((sum-of-squares 0.0) (buffer (make-vector 128 0.0)) (position 0) (current-sample 0) (chan-samples (frames))) (lambda (y) (if y (let ((old-y (vector-ref buffer position))) (set! sum-of-squares (- (+ sum-of-squares (* y y)) (* old-y old-y))) (vector-set! buffer position y) (set! position (1+ position)) (if (= position 128) (set! position 0)) (set! current-sample (1+ current-sample)) (if (> sum-of-squares silence) (if (= current-sample chan-samples) ;; at end return trailing samples as long as it looks like sound (let ((temp-buffer make-vector 128 0.0)) (do ((i 0 (1+ i))) ((= i 128) temp-buffer) (let ((final-y (vector-ref buffer position))) (vector-set! temp-buffer i (if (> sum-of-squares silence) final-y replacement)) (set! sum-of-squares (- sum-of-squares (* final-y final-y))) (set! position (1+ position)) (if (= position 128) (set! position 0))))) old-y) replacement)) #f))))) (map-chan (map-silence .01 0.0)) ; squelch background noise (map-chan (map-silence .001 #f)) ; remove silences altogether
In case it isn't obvious, we're using buffer to hold a running portion of the sound, and sum-of-squares to hold the sum of the squares of all the samples in that portion. When the portion's sum falls below the argument silence, we replace the current sample with replacement. At the end, we flush out all the remaining samples awaiting output in buffer.
As with the mapping functions, it is sometimes inconvenient to handle the entire current sound in one array, and the undo/redo lists are easier to use if each actual edit is just one listed edit in Snd. If the mapping functions don't provide the kind of access you want, you can also write sound files, then tell Snd that the data in the file consitutes the new data in the sound currently being edited.
open-sound-file (name chans srate comment) ; returns fd vct->sound-file (fd vct vals) ; writes vals floats to fd close-sound-file (fd vals)After opening the file, loop through the data calling samples->vct, deal with the vct data as desired, write the samples to the file via vct->sound-file, then when finished, close-sound-file. If the new data is to replace the old, call set-samples with the new sound file's name; otherwise call insert-samples.
Most of the underlying sound library (Sndlib) functions are available. These return -1 if the file can't be found or some other error occurs.
sound-samples (filename) samples of sound according to header (can be incorrect) sound-frames (filename) frames of sound according to header (can be incorrect) sound-duration (filename) duration of sound in seconds sound-datum-size (filename) bytes per sample sound-data-location (filename) location of first sample (bytes) sound-chans (filename) number of channels (samples are interleaved) sound-srate (filename) sampling rate sound-header-type (filename) header type (e.g. aiff-sound-file) sound-data-format(filename) data format (e.g. 16-linear) sound-length (filename) true file length (bytes) sound-type-specifier (filename) original header type identifier sound-type-name (type) e.g. "AIFF" sound-format-name (format) e.g. "16-bit big endian linear" sound-comment (filename) header comment, if any sound-bytes-per-sample (format) bytes per sample audio-error () returns error code indicated by preceding audio call audio-error-name(err) string decription of error code describe-audio () describe audio hardware state (in help window) set-oss-buffers (num size) in Linux (OSS) sets the number and size of the OSS "fragments"
When Snd starts up, it looks for an "initialization file", normally named "~/.snd". This optional file is supposed to be just like emacs' .emacs file, containing any customizations or extensions that you want loaded whenever Snd starts up. For example, say we want the Snd window to start out 800x500, want to predefine an envelope named "env1", and want the file selection box to default to showing just sound files. We make ~/.snd and put in it:
(set-window-width 800) (set-window-height 500) (defvar env1 '(0 0 1 1 2 0)) (set-just-sounds 1)
In addition, we could add our own analysis functions or whatever. In more complex situations, you may want an initialization file particular to a given machine, and global across users; in that case, the macro SND_CONF gives the name of this global initialization file. At ccrma, it's "/etc/snd.conf". The global file is read before the user's local file; both can, of course, be absent. To override reading the global init file when Snd is invoked, include the switch -noglob. To override the local init file, use -noinit. To set the global file name in a makefile include (for example) -DSND_CONF='"/home/bil/cl/sndconf"' in CFLAGS.
As a more extended example, here is my initialization file (or, I wish it were this neat; the actual file is more full of junk than my garage):
(use-modules (ice-9 popen) (ice-9 debug)) (set-window-width 500) (set-window-height 300) (set-window-y 50) (set-window-x 300) (defvar env1 '(0 1 1 2 2 1)) (set-show-mix-waveforms #t) (define shell (lambda (cmd) (let* ((str "") (fil (open-pipe cmd "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) str))) (define beige (make-color 0.96 0.96 0.86)) (define blue (make-color 0 0 1)) (set-selected-graph-color beige) (set-selected-data-color blue)
These examples are simplified to make the exposition cleaner; see examp.scm for more robust versions. examp.scm also has examples that add and remove menu items, set variables via the special "F" keys, perform correlation on the current data, make a system call from the listener, and so on. The following function computes the rms amplitude of a region:
(define region-rms (lambda (n) (let* ((data (region-samples 0 0 n)) (len (vector-length data)) (sum 0.0)) (do ((i 0 (1+ i))) ((= i len) (sqrt (/ sum len))) (set! sum (+ sum (* (vector-ref data i) (vector-ref data i))))))))
To get the data currently displayed in the time domain window:
(define window-samples (lambda () (let ((wl (left-sample)) (wr (right-sample))) (samples wl (+ 1 (- wr wl))))))
Now we can use window-samples and graph-hook to show a running graph of the time domain energy:
(define display-energy (lambda () (let* ((data (window-samples)) (len (vector-length data))) (do ((i 0 (1+ i))) ((= i len)) (vector-set! data i (* (vector-ref data i) (vector-ref data i)))) (graph data)))) (set! graph-hook "(display-energy)")
As the time domain window is moved, the lisp window automatically updates itself. The same thing can show the spectral energy (via transform-samples). Unfortunately, vector access and floating-point multiplies are slow in Guile, so in a case like this, it will speed up redisplay by at least an order of magnitude to use the 'vct' functions. The functions graph, fft, insert-samples, and set-samples know about the vct structure also, so we can rewrite display-energy:
(define display-energy (lambda (snd chn y0 y1) (let* ((ls (left-sample snd chn)) (rs (right-sample snd chn)) (data (samples->vct ls (+ 1 (- rs ls)) snd chn)) (len (vc-length data)) (sr (srate snd))) (vct-multiply! data data) (graph data "energy" (/ ls sr) (/ rs sr) 0.0 (* y1 y1) snd chn))))
See examp.scm for more examples of using the vct structure. Say we want Snd to refuse to exit if there are unsaved edits.
(define unsaved-edits? (lambda (ind) (and (< ind (max-sounds)) (or (and (ok? ind) (> (vector-ref (edits ind) 0) 0) (report-in-minibuffer "there are unsaved edits") #t) (unsaved-edits? (+ ind 1)))))) (add-hook! exit-hook (lambda () (report-in-minibuffer "") (unsaved-edits? 0)))
Here's somewhat brute-force code to play a sound a given number of times:
(define plays 0) (define pl1 (lambda (snd) (if (= plays 0) (remove-hook! stop-playing-hook pl1) (begin (set! plays (- plays 1)) (play 0 snd))))) (define (pl n) (set! plays (- n 1)) (add-hook! stop-playing-hook pl1) (play)) (bind-key (char->integer #\p) 0 (lambda () (pl (max 1 (prefix-arg)))) #t)
Say we are so annoyed by the X/Motif file browser that we want Snd to exit back to the shell if its file argument is not found (this code obviously has to be in the init file):
(define no-startup-file? (lambda (ind file) (if (= ind (max-sounds)) (begin (write (string-append "can't open " file) (current-error-port)) (newline (current-error-port)) #t) (if (ok? ind) #f (no-startup-file? (+ ind 1) file))))) (add-hook! start-hook (lambda (file) (if (> (string-length file) 0) (no-startup-file? 0 file) #f)))
And just for completeness, here's an example of using the fft-hook. Since fft's in Snd are asynchronous and interruptible, there are times when the function transform-samples returns nil (the fft in question is still in progress, for example).
(define fft-peak (lambda (snd chn scale) (if (and (ffting) (= (fft-style) normal-fft)) (let ((samps (transform-samples snd chn))) (if samps (let* ((len (vector-length samps)) (mx (vector-ref samps 0)) (peak (do ((i 1 (+ i 1))) ((= i len) (/ (* 2 mx) (fft-size))) (let ((val (abs (vector-ref samps i)))) (if (> val mx) (set! mx val)))))) (report-in-minibuffer (number->string peak) snd))))) #f)) (add-hook! fft-hook fft-peak)
The following function uses the sndlib functions to mimic the 'info' popup menu option (see examp.scm for a version that uses format):
(define info (lambda (file) (string-append file ": chans: " (number->string (sound-chans file)) ", srate: " (number->string (sound-srate file)) ", " (sound-type-name (sound-header-type file)) ", " (sound-format-name (sound-data-format file)) ", len: " (number->string (/ (sound-samples file) (* (sound-chans file) (sound-srate file)))))))
There are a few X-style resources that Snd explicity looks for (see Snd.ad):
initFile "~/.snd" epsFile "snd.eps" overwriteCheck 0 groups 6 autoResize 1 groupOutChans 4 horizontalPanes 0 buttonFont -*-times-medium-r-*-*-14-*-*-*-*-*-iso8859-1 boldbuttonFont -*-times-bold-r-*-*-14-*-*-*-*-*-iso8859-1 axisLabelFont -*-times-medium-r-normal-*-20-*-*-*-*-*-iso8859-1 axisNumbersFont -*-courier-medium-r-normal-*-14-*-*-*-*-*-iso8859-1 helpTextFont 9x15 listenerFont default useSchemes none highlightcolor ivory1 basiccolor ivory2 positioncolor ivory3 zoomcolor ivory4 cursorcolor red selectioncolor lightsteelblue1 mixcolor lightgreen mixfocuscolor green2 listenercolor aliceblue envedwaveformcolor blue filterwaveformcolor blue mixwaveformcolor darkgray graphcolor white selectedgraphcolor white datacolor black selecteddatacolor black markcolor red pushedbuttoncolor lightsteelblue1 sashcolor lightgreen
If you have the XmHTML widget loaded, the following resources are also available:
htmlDir "." ! also the variable html-dir htmlWidth 600 htmlHeight 400 htmlFontSizeList "14,10,24,24,18,14,12" htmlFixedFontSizeList "14,10"
You can experiment with other choices by using the -xrm command line argument:
snd -xrm '*Highlightcolor: Red' oboe.snd snd -xrm '*AxisNumbersFont: 6x10' oboe.snd snd -xrm '*overwriteCheck: 1' oboe.snd snd -xrm '*useSchemes: all' -xrm '*scheme: Pacific' snd -xrm '*fontList: 9x15' oboe.snd snd -xrm '*listenerFont: 6x10' oboe.snd snd -xrm '*mixwaveformcolor: red' oboe.snd -notebook snd oboe.snd pistol.snd -xrm '*graphcolor: beige' -xrm '*selecteddatacolor: red' snd oboe.snd -title hiho -display hummer.hiho:0.0 -xrm '*chn-graph*backgroundPixmap: text.xpm'
The color names can be found in rgb.scm. If you use SGI color schemes (the useSchemes resource), most of the color resources mentioned above are ignored (the cursor and selection colors are never ignored). If color schemes are available they're listed in /usr/lib/X11/schemes, probably -- it's unfortunate that there is the language Scheme used by Guile, and the notion of an SGI color scheme -- there is no connection between the two. The last example sets the window title to "hiho", rather than "snd", displays the window on the machine hummer.hiho (presumably accessible over the net), and tiles the graph backgrounds with the contents of text.xpm. To get the -geometry argument to work, set the autoResize resource to 0:
snd oboe.snd -geometry 800x200 -xrm '*autoResize: 0'
The AutoResize resource determines how Snd acts when files are added or removed from its overall display. The default (1) causes Snd to expand or contract the main window's size to accomodate the sounds (many people find this distracting); if autoResize is 0, the outer window size remains the same, and the sounds try to fit as best they can. See also the variable auto-resize. If overwriteCheck is 1, Snd asks before overwriting existing files. The resource groupOutChans sets the number of output channels in a mixer group. Until the groups are created (upon invoking the mixer), this number grows as needed to reflect the maximum number of channels seen in any file read by Snd. 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; if 3, the sounds are enclosed in a scrolling widget. These special cases are not as fully supported as the default vertical layout.
basiccolor default background color everywhere; basic-color cursorcolor color of the cursor; cursor-color datacolor unselected data color; data-color envedwaveformcolor color of envelope editor waveform; enved-waveform-color filterwaveformcolor color of control panel filter waveform; filter-waveform-color graphcolor unselected channels' graph background; graph-color highlightcolor highlighting here and there; highlight-color listenercolor background color of the listener; listener-color markcolor color of the mark indicator; mark-color mixcolor used for mixer console titles; mix-color mixfocuscolor mix within current group indicator; mix-focus-color mixwaveformcolor color of mix waveform data; mix-waveform-color positioncolor color of position sliders; position-color pushedbuttoncolor color of pushed button; pushed-button-color sashcolor color of paned window sash handles selecteddatacolor color of the data in selected channel; selected-data-color selectedgraphcolor background of selected channel's graph; selected-graph-color selectioncolor color of an active selection; selection-color textfocuscolor color of text field with focus; text-focus-color zoomcolor color of zoom sliders; zoom-color
Each of these colors can be set in Guile using the second name given above ("basic-color").
Colors are defined by calling make-color with the three red/green/blue values,
each a float between 0.0 and 1.0. (set-basic-color (make-color 1.0 0.0 0.0))
sets
the overall background color of Snd to red. rgb.scm defines all the standard X11 color names
(you probably don't want to load the whole thing; just use the names as needed).
There are several other resources that set various widget sizes: zoomSliderWidth, positionSliderWidth, toggleSize, sashSize, sashIndent, channelSashSize, channelSashIndent, and envedPointSize. And several more color resources: whitecolor (list background), blackcolor (recorder VU meter text), redcolor (buttons, VU clipping, etc), greencolor (a few buttons), yellowcolor (a few envelope editor buttons), lightbluecolor (the recorder), and lighterbluecolor (the fft option panel).
The following flags are recognized by Snd (leaving aside all the usual 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) -scroller layout sounds vertically in a scroller widget -separate layout sounds each in a separate window (lisp listener in main window) --help print some help, version info, and exit --version print version info -noglob don't read SND_CONF, if any -noinit don't read ~/.snd, if any -p -preload <dir> preload sound files in directory <dir> (for example, snd -p .) -l -load <file> load guile (scheme) code in <file> (for example, snd -l test.scm) <file> -s <4 args> set initial graph window bounds -e -eval expr evaluate expr
The -e switch evaluates its argument as though it had been passed to M-X. The initialization file, if any, is loaded first, then the arguments are processed in order. For example
snd -e "(set-data-color (make-color 1 0 0))" oboe.snd
reads ~/.snd, if any, then sets the (unselected) data color to red, then opens oboe.snd.
./snd -eval '(begin (display (+ 1 2)) (exit))'
prints "3" and exits.
It is possible to load your own C code into Snd at run-time, either as a simple dynamically loaded module, or as a "plug-in"; you can also use any external program from within Snd as an editing function.
You can import shared object files into Snd at any time.
You need to build Snd
with -lguile (that is, load it with the guile shared library, not libguile.a);
if the loader can't find libguile.so.2 (or whatever), add its directory to
your LD_LIBRARY_PATH; for example, if
it's on /usr/local/lib, setenv LD_LIBRARY_PATH /usr/local/lib
.
Next add Guile wrappers to your C code:
/* cscm.c */ #include <math.h> #include <stdio.h> #include <stdlib.h> #include <guile/gh.h> int hiho (int a) { /* this is the function we want to call from Snd */ return(1+a); } SCM hiho_wrapper(SCM a) { /* this tells Guile how to interpret the arguments and return value of hiho */ return(gh_int2scm(hiho(gh_scm2int(a)))); } void init_hiho() { /* this declares hiho within Guile calling the wrapper which calls the C function hiho */ gh_new_procedure1_0("hiho",hiho_wrapper); }
Next compile your code into a shared object (this example is for Linux):
cc -c cscm.c ld -shared -o cscm.so cscm.o -lguile
Now go to Snd's lisp listener and,
(define lib (dynamic-link "/home/bil/cl/cscm.so")) (dynamic-call "init_hiho" lib) (hiho 3)
The function we actually want loaded into Guile here is "hiho". We define a wrapper for it to handle the translation between Guile (Scheme) variable types and C ("hiho_wrapper"), and a procedure to define hiho in Guile ("init_hiho"). Once loaded ("dynamic-link"), we can call the initialization function ("dynamic-call"), and thereafter treat "hiho" as though it had been defined in Guile/Snd to begin with. After both the dynamic-link and dynamic-lib calls, the listener will print "#<unspecified>" or something equally obscure to indicate in its own peculiar way that all went well. M-x (hiho 4) will print 5 in the minibuffer.
Simple dynamically loaded modules like this, however, do not have any better access to the editing aspects of Snd than the equivalent Guile (Scheme) code. Perhaps the main reason to work at this level is to speed up code that manipulates large vectors:
/* cscm.c */ #include <math.h> #include <stdio.h> #include <stdlib.h> #include <guile/gh.h> typedef struct {int length; float *data;} vct; SCM hiho (SCM data) { /* this is the function we want to call from Snd -- * it will return the sum of the squares of the samples */ int i; float sum = 0.0; vct *v = (vct *)gh_cdr(data); for (i=0;i<v->length;i++) sum += (v->data[i] * v->data[i]); return(gh_double2scm(sum)); } void init_hiho() {gh_new_procedure1_0("hiho",hiho);}
Now in Snd, we'd call this with something like:
(hiho (samples->vct 0 200))
However, our function can't be interrupted, or applied to a selection, or follow the sync buttons, and so on. To make your editing function act like any of the predefined Snd functions, tie it into Snd as a "plug-in" (I got this terminology from Gimp).
A plug-in is C code loadable at run-time (a shared object normally). It contains a set of functions that implement the desired operation and tell Snd how to handle it. A very brief example follows.
/* scale.c */ #include "snd.h" /* snd plug-ins are described in "snd-plugins.h" */ static int sample_ready = 0, current_sample = 0; static int send_sample(int *new_value) { /* called by Snd when it wants the next sample of the new data */ if (sample_ready) { (*new_value) = current_sample; sample_ready = 0; return(PLUG_OK); } else return(PLUG_NO_DATA); } static int receive_sample(int value, int last_value) { /* called by Snd on each sample of the current data (the data being edited) */ /* last_value will be true only if this is the last sample of data in the current channel */ /* these values are 32-bit integers normally between -32768 and 32767 */ sample_ready = 1; current_sample = value * 2; /* this is our scaling operation */ return(PLUG_OK); } static snd_plug *plug = NULL; SCM snd_scale(void) { if (plug == NULL) { plug = (snd_plug *)calloc(1,sizeof(snd_plug)); /* we don't need any fanciness here, so several fields are null */ plug->init = NULL; plug->quit = NULL; plug->start_channel = NULL; plug->end_channel = NULL; plug->read_sample = receive_sample; plug->write_sample = send_sample; plug->name = "scale"; plug->documentation = "scales by 2"; plug->edit_name = "(snd-scale)"; scm_sysintern("scale-plug",gh_ulong2scm((unsigned long)plug)); /* this tells Guile about the new plug -- we'll refer to it by the name "scale-plug" in Guile */ } gh_eval_str("(call-plug scale-plug)"); /* this actually calls the plug-in function from within Snd */ return(SCM_BOOL_F); } void init_snd_scale(void) { /* define "snd-scale" in Guile as a function of no arguments that calls snd_scale */ gh_new_procedure("snd-scale",snd_scale,0,0,0); } /* end of scale.c */
Now we need to compile and load this code, load it into Snd, and declare snd_scale:
cc scale.c -c -DHAVE_GUILE -o scale.o ld -shared scale.o -o scale.so
Then in Snd (this could obviously be in your Snd initialization file):
(define lib (dynamic-link "/space/home/bil/cl/scale.so")) (dynamic-call "init_snd_scale" lib)
If all went well, we've loaded our module and Snd can now call it via M-x (snd-scale). Another more elaborate example is anoi.c, a denoiser by Matti Koskinen, included in the Snd distribution.
The Guile functions that relate to plug-ins are:
call-plug (plug) invoke the plug as an editing operation. call-plug-selection (plug) invoke the plug as an editing operation over current selection. describe-plug (plug) describe plug
When a plug-in edit operation is invoked in Snd: plug->init is called with a pointer to the global Snd state if it returns PLUG_OK, we continue, else we print some message and exit the edit plug->edit_name is saved as the edit-history's reference to the current operation the 'sync' buttons are checked to collect the set of channels affected a loop begins through the set of channels, one channel at a time at any time, the operation may be aborted (by the user), thereby setting state->stopped_explicitly to true plug->start_channel is called with the current channel if it returns PLUG_OK we continue, else we abort the current edit and try to return to the pre-edit state a loop begins through the current samples of the current channel for each sample, plug->read_sample is called with the current sample and a flag indicating the end of the channel's data if it returns PLUG_OK we continue, else we try to back out plug->write_sample is called until it returns PLUG_NO_DATA each returned sample (if any) is saved as the edited form of the current channel's data for each new sample, write_sample should return PLUG_OK if it returns PLUG_ERROR or PLUG_STOP, we abort the edit and try to back out when this channel's data is done, plug->end_channel is called with the channel if it returns PLUG_OK, the channel is considered edited, and its edit history is updated when all the channels have been edited, plug->quit is called with the global state
If any of the functions is null, it is ignored (as though it had simply returned PLUG_OK). If PLUG_ERROR is returned, Snd prints plug->error as its error message if it isn't null. The guile functions call-plug, call-plug-selection, and describe-plug take the plug argument. snd-plugins.h has a variety of macros to get information about the current channel and whatnot. You can also call any of the Snd functions from your module via gh_eval_str:
gh_eval_str("(recorder-dialog)"); gh_eval_str("(open-sound \"oboe.snd\")"); srate = gh_scm2int(gh_eval_str("(srate)"));See also Matti Koskinen's plugin package) and the echo plug-in below.
Any external program that knows about sound files can be used to perform editing operations from Snd. You thereby get Snd's display, analysis, header and format conversion, and edit-tree support, and can concentrate on the actual sound effect you're developing. The original impetus for Snd came from CLM, a large lisp-listener based program which normally runs without a graphical user interface, and without any simple way to move around in what Snd calls the edit history. Since interprocess communication proved problematic in this case, the communication path was simplified to consist of little more than shared files, with CLM treated as a batch program. A nice side-effect of this is that any other program can fit the same mold.
For example, say we have a sound processing CLM instrument we like; it takes two sound file names as its arguments, reading the first and writing the second. In Snd we write the current edited state to a temporary file, start CLM, call the instrument passing it the input and output filenames, then pass its output back to Snd. Snd then replaces the current data with the data our instrument wrote, as if it had incorporated that instrument as an editing operation from the beginning.
There are two choices as to what is being edited: either the full sound, or the current active selection of it. And two choices as to how this data should be presented to the external program: either as one, possibly multi-channel file, or as a set of mono files. So we have eight functions (four to write, then four to read), as well as one function to get the current temporary file names (as written by Snd, to be read by the external program):
sound-to-temp (type format) write out sync'd edit state as a temp file sound-to-temps (type format) write out sync'd edit state as temp files selection-to-temp (type format) write out selected data as a temp file selection-to-temps(type format) write out selected data as temp files temp-filenames (data) return vector of temp file names temp-to-selection (data name origin) read selected data from temp file temps-to-selection(data names origin) read selected data from temp files temp-to-sound (data name origin) read sync'd edit state from temp file temps-to-sound (data names origin) read sync'd edit state from temp files
Everything else is handled by scheme code. The type and format arguments default to the currently selected sound's header type and data format, but if your external program can only read a particular kind of file or data, you can specify them here. The external program should not delete either input or output files, and should not overwrite existing output files (Snd handles this bookkeeping).
STK is a synthesis toolkit developed by Perry Cook and Gary Scavone. Like many such programs, it reads a score file and produces an output file. We'll use it here to replace the current sound with a clarinet tone:
(define stk (lambda () (let* ((str "") (data (sound-to-temp)) (fil (open-pipe "syntmono Clarinet -s /tmp/test < scores/hiho.ski" "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) (temp-to-sound data "/tmp/test.snd" "(STK clarinet)") str)))
hiho.ski is:
NoteOn 0.000000 1 60 127.000000 NoteOff 0.126032 1 60 63.500000
The basic sequence is: sound-to-temp writes out the current (possibly edited) state of the selected sound(s) in Snd as a temp file. sound-to-temp returns an opaque object which we will later pass to temp-to-sound to complete the edit. But first, we open a pipe, call STK as a batch job, and read in whatever it prints out (so we can see how the call went). Then we call temp-to-sound passing it the object mentioned earlier, the new filename (the data written by STK that will replace the current data in Snd), and the associated edit-history reference to the operation. In brief:
[sound | selection]-to-[temp | temps] call external program on the data and write new data [temp | temps]-to-[sound | selection]
But this function can't safely be called twice because it always writes "test.snd", and it isn't very useful as an editing operation because it completely ignores the current Snd data. The next steps are to write our data using safe temporary filenames, and read the current data using temp-filenames. We'll also apply this to the current selection, rather than the full file. Since I don't know enough about STK to get it to read an input file, I'll use Sox for the next examples.
Sox is a widely available and well-known program for sound format conversions and various sound effects. In this case, we'll read and write NeXT files, and use Sox's copy "effect".
(define sox (lambda () (let ((data (selection-to-temp))) (if data (let* ((str "") (input-names (temp-filenames data)) (output-name (string-append (tmpnam) ".snd")) (cmd (string-append "sox -t .au \"" (vector-ref input-names 0) "\" -t .au \"" output-name "\" copy")) (fil (open-pipe cmd "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) (temp-to-selection data output-name "(sox copy)") str) (report-in-minibuffer "no current selection")))))
We use the Guile built-in function tmpnam to get an output file name that doesn't collide with any existing file; We then read the incoming filename that Snd wrote (temp-filenames), and pass that to Sox. This is a very complicated no-op, since Sox in this case merely copies its input to its output. We're assuming NeXT/Sun files (the "-t .au" business), and we're blithely ignored the possibility that we might be editing any number of sounds, each with any number of channels. To deal with the latter, we need to notice how many mono files have been passed to us (in the case of sound-to-temps), or our external program needs to be able to handle a file with arbitrarily many channels (sound-to-temp). In the next example, we'll loop through the mono files, processing each in turn. We'll also start packaging up the boilerplate a bit.
(define execute-and-wait (lambda (cmd) (let ((str "") (fil (open-pipe cmd "r"))) (do ((val (read-char fil) (read-char fil))) ((eof-object? val)) (set! str (string-append str (string val)))) (close-pipe fil) str))) (define loop-through-files (lambda (description make-cmd) (let* ((data (sound-to-temps)) (input-names (temp-filenames data)) (files (vector-length input-names)) (output-names (make-vector files ""))) (do ((i 0 (1+ i))) ((= i files)) (vector-set! output-names i (string-append (tmpnam) ".snd")) (execute-and-wait (make-cmd (vector-ref input-names i) (vector-ref output-names i)))) (temps-to-sound data output-names description)))) (define sox-1 (lambda () (loop-through-files "(sox copy)" (lambda (in out) (string-append "sox -t .au \"" in "\" -t .au \"" out "\" copy")))))
Now our sox function can handle any number of files or channels that might be sync'd together in Snd. In case it's not obvious, the function loop-through-files takes as its second argument a function of two arguments, and calls it on each file as we march through the input file list, passing it the input and output file names as arguments. It (make-cmd) puts together the actual call on sox that we were making earlier. An equivalent using cp is:
(define copyfile (lambda () (loop-through-files "(cp)" (lambda (in out) (string-append "cp " in " " out)))))
But we're still assuming NeXT/Sun format files, and we're throwing away the string we so laboriously created. A more friendly function would display its progress.
Reading, mixing, and writing sound files are no problem in CLM, but
it's unusual to run it as a batch program.
Assume for the moment we have loaded the CLM instruments we want (v.ins and jcrev.ins),
and have saved the image using ACL 5.0 in Linux. The CLM image is
invoked in this case with lisp -I clm.dxl
. ACL provides
a way (-e) to evaluate lisp code from the command line, so
we'll use that along with the exit function to turn CLM
into a batch program. For example, we can reverberate the current data:
(define reverb (lambda (reverb-amount) (loop-through-files (string-append "(reverb " (number->string reverb-amount) ")") (lambda (in out) (string-append "lisp -I clm.dxl " "-e '(progn (restart-clm) " " (with-sound (:play nil :output \"" out "\" :reverb jc-reverb) " " (mix \"" in "\") " " (mix \"" in "\" :output *reverb* :amplitude " (number->string reverb-amount) "))" " (exit))'")))))
This is a call on CLM's with-sound with a reverberator
and two calls on mix, one for the direct signal, the
other for the reverb input. The with-sound form is wrapped up
in a progn that calls restart-clm (to make sure all dynamically
allocated entities are setup properly), the with-sound itself,
then exit to leave lisp (the latter is needed since we're
waiting for EOF in the execute-and-wait function).
The reverb function's argument sets the amount of
reverb, and we save that value in the edit-history descriptor.
Now, in Snd, M-x (reverb .1)
reverbs the current
data and extends the edit-history list with the string "(reverb .1)".
This example also shows how to mix something into the current
data. For example, to add an fm-violin note starting at the
current cursor:
(define fm-violin (lambda (dur frq amp) (let* ((beg (/ (cursor) (srate))) (fmv-call (string-append "(fm-violin " (number->string beg) " " (number->string dur) " " (number->string frq) " " (number->string amp) ")"))) (loop-through-files fmv-call (lambda (in out) (string-append "lisp -I clm.dxl " "-e '(progn (restart-clm) " " (with-sound (:play nil :output \"" out "\") " " (mix \"" in "\") " fmv-call " ) (exit))'"))))))
But if anything goes wrong, the whole process gets hung, since Lisp drops into its error handler, and Snd is waiting for the Lisp job to exit -- we have to go to a shell and kill the Lisp subjob! So let's check for C-g in Snd, and send the subjob output to Guile's "current-output-port" (whatever that is):
(define read-or-run (lambda (fil) (let ((val (peek-char fil))) (or (and val (read-char fil)) (abort?) (read-or-run fil))))) (define execute-and-wait (lambda (cmd) (let ((fil (open-pipe cmd "r"))) (do ((val (read-or-run fil) (read-or-run fil))) ((or (eq? val #t) (eof-object? val)) (eq? val #t)) (write-char val (current-output-port))) (close-pipe fil)))) (define loop-through-files (lambda (description make-cmd) (let* ((data (sound-to-temps)) (input-names (temp-filenames data)) (files (vector-length input-names)) (output-names (make-vector files "")) (stopped #f)) (do ((i 0 (1+ i))) ((or stopped (= i files))) (vector-set! output-names i (string-append (tmpnam) ".snd")) (set! stopped (execute-and-wait (make-cmd (vector-ref input-names i) (vector-ref output-names i))))) (temps-to-sound data output-names description))))
If this is too ugly, we could probably use append-to-minibuffer instead of write-char. In Clisp, use the -x switch without the exit function call. Also, place the expression to be evaluated in double quotes, rather than ACL's single quotes.
To include the entire Snd editor as a widget in some other program, first compile it with -DSND_AS_WIDGET. Then load it into your program, using the procedure snd_as_widget to fire it up. The program saw.c included with Snd is a very brief example.
void snd_as_widget(int argc, char **argv, XtAppContext app, Widget parent, Arg *caller_args, int caller_argn)
starts up the Snd editor in the widget parent, passing the outer Snd form widget the arguments caller_args and caller_argn. The enclosing application context is app. parent needs to be realized at the time of the call, since Snd uses it to set up graphics contexts and so on. argc and argv can be passed to simulate a shell invocation of Snd. Remember that in this case, the first string argument is expected to be the application name, and is ignored by Snd.
The files clm.c, clm.h, and clm2scm.c implement CLM (a Common Lisp Music V implementation described in clm.html, available in clm-2.tar.gz at ccrma-ftp) as a Guile-loadable module. You can have them loaded into Snd by including the --with-clm switch when running configure, or by using -DWITH_MUS_MODULE with CLM_O_FILES in your makefile (see makefile.clm for an example). Or, the CLM module can be loaded at any time as a shared library. In Linux, the command sequence is something like:
cc clm.c -c -O2 -DHAVE_GUILE -DHAVE_SNDLIB -DLINUX cc clm2scm.c -c -O2 -DHAVE_GUILE -DHAVE_SNDLIB -DLINUX setenv LD_LIBRARY_PATH /usr/local/lib:. ld io.o audio.o headers.o sound.o clm.o vct.o clm2scm.o -o clm.so -lc -lm -shared
This creates the shared library clm.so, and sets the LD_LIBRARY_PATH environment variable to include the current directory (the "." at the end); now we start Snd, and in the lisp listener:
(define clmlib (dynamic-link "clm.so")) (dynamic-call "init_mus2scm_module" clmlib)You can see what a generator does, or a group of generators, by running them in the lisp listener, and using the graph and spectrum functions. For example, say we have these declarations in ~/.snd:
(define data-size 1024) (define data (make-vct data-size)) (define run (lambda (fun) (do ((i 0 (1+ i))) ((= i data-size)) (vct-set! data i (fun))) (graph data))) (define runf (lambda (fun) (do ((i 0 (1+ i))) ((= i data-size)) (vct-set! data i (fun))) (graph (spectrum data blackman2-window data-size #t))))
Now we can open the listener, and type:
(define hi (make-oscil)) (run (lambda () (oscil hi))) (define ho (make-oscil)) (runf (lambda () (oscil hi (* .5 (oscil ho)))))
Obviously, any CLM instrument or function can be used in this way to edit sounds, and so on. Say we want an echo effect:
(define echo (lambda (scaler secs) (let ((del (make-delay (round (* secs (srate)))))) (lambda (inval) (if inval (+ inval (delay del (* scaler (+ (tap del) inval))))))))))
For readers who are new to Scheme, echo is a function of two arguments, scaler and secs. Scaler sets how loud subsequent echos are, and secs sets how far apart they are in seconds. echo uses the secs argument to create a delay line (make-delay) using the current sound's sampling rate to turn the secs parameter into samples. echo then returns a "closure", that is, a function with associated variables (in this case del and scaler); the returned function (the second lambda) takes one argument (inval) and returns the result of passing that value to the delay with scaling. The upshot of all this is that we can use:
(map-chan (echo .5 .75) 0 44100)
to take the current active channel and return 44100 samples of echos, each echo half the amplitude of the previous, and spaced by .75 seconds. map-chan's first argument is a function of one argument, the current sample; when we pass it (echo ...), it evaluates the echo call, which returns the function that actually runs the delay line, producing the echo. The CLM (common lisp) version might be something like:
(definstrument echo (beg dur scaler secs file) (let ((del (make-delay (round (* secs *srate*)))) (inf (open-input file)) (j 0)) (run (loop for i from beg below (+ beg dur) do (let ((inval (ina j inf))) (outa i (+ inval (delay del (* scaler (+ (tap del) inval))))) (incf j)))) (close-input inf))) ;;; (with-sound () (echo 0 60000 .5 1.0 "pistol.snd"))I hope someday to make it possible to use this form directly in Snd, but for now it needs to be translated. Unfortunately, the interpreted Scheme version of the echo effect is pretty slow; it is very handy for experimenting, but when we have the effect we want, it can be bothersome to have to wait while it plows through a long file. To optimize it in Snd, we'll use exactly the same code (the CLM-style delay line), but translate it to C, loading the resultant module into Snd as described above under dynamically loaded modules. Here is our module (echo.c):
#include "snd.h" #include "clm.h" static int sample_ready = 0, current_sample = 0; static mus_any *del = NULL; static float echo_scaler,echo_secs,echo_duration; static int trailing_samps = 0; static chan_info *echo_cp; static int send_sample(int *new_value) { if (sample_ready) { (*new_value) = current_sample; sample_ready = 0; return(PLUG_OK); } else { if (trailing_samps > 0) { (*new_value) = (int)mus_delay(del,echo_scaler * mus_tap(del,0.0),0.0); trailing_samps--; return(PLUG_OK); } } return(PLUG_NO_DATA); } static int receive_sample(int value, int last_value) { sample_ready = 1; current_sample = (int)(value + mus_delay(del,(float)((value + mus_tap(del,0.0)) * echo_scaler),0.0)); if (last_value) { trailing_samps = (echo_duration * SND_PLUGIN_SRATE(echo_cp)) - SND_PLUGIN_SAMPLES(echo_cp); if (trailing_samps < 0) trailing_samps = 0; } return(PLUG_OK); } static int init_channel(chan_info *cp) { int samples; if (del) mus_free(del); /* might be cleaner to put this in the end_channel method */ samples = (int)(echo_secs * SND_PLUGIN_SRATE(cp)); del = mus_make_delay(samples,NULL,samples); trailing_samps = 0; echo_cp = cp; return(PLUG_OK); } static snd_plug *plug = NULL; SCM echo(SCM scaler, SCM secs, SCM duration) { echo_scaler = (float)gh_scm2double(scaler); echo_secs = (float)gh_scm2double(secs); echo_duration = (float)gh_scm2double(duration); if (plug == NULL) { plug = (snd_plug *)calloc(1,sizeof(snd_plug)); plug->init = NULL; plug->quit = NULL; plug->start_channel = init_channel; plug->end_channel = NULL; plug->read_sample = receive_sample; plug->write_sample = send_sample; plug->name = "echo"; plug->documentation = "echos"; plug->edit_name = "(echo)"; scm_sysintern("echo-plug",gh_ulong2scm((unsigned long)plug)); } gh_eval_str("(call-plug echo-plug)"); return(SCM_BOOL_F); } void init_echo(void) { gh_new_procedure("echo",echo,3,0,0); }
Most of this is boiler-plate that can be quickly copied and pasted. Once we have echo.c, we need to create the associated shared library. In Linux, this would be something like:
setenv LD_LIBRARY_PATH /usr/local/lib:/space/home/bil/cl ld -shared clm.o io.o headers.o audio.o sound.o -o sndlib.so cc echo.c -c -DHAVE_GUILE -DWITH_MUS_MODULE -DHAVE_SNDLIB ld echo.o -shared -o echo.so sndlib.so -lc -lm
Now in Snd:
(define echolib (dynamic-link "echo.so")) (dynamic-call "init_echo" echolib) (echo .75 .5 3.0)
which is about 10 times as fast as the interpreted version. The trailing_samps business allows our plug-in function to return more samples than were in the original. (In more complex cases, such as the fm-violin in examp.scm, translation to C gives us a speed up of about a factor of 40 -- exactly equivalent to using the run macro in CLM).
See clm.html for full details. Optional args are in italics.
all-pass (gen input pm) all-pass filter all-pass? (gen) #t if gen is all-pass filter amplitude-modulate (carrier in1 in2) amplitude modulation array-interp (arr x) interpolated array lookup array->file (filename vct len srate channels) write the contents of vct to the newly created sound file filename, giving the new file channels channels (data assumed to be interleaved in vct), sampling rate srate, and len samples (not frames). asymmetric-fm (gen index fm) asymmetric-fm generator asymmetric-fm? (gen) #t if gen is asymmetric-fm generator buffer->frame (gen frame buffer generator returning frame buffer->sample (gen) buffer generator returning sample buffer-empty? (gen) #t if buffer has no data buffer? (gen) #t if gen is buffer generator clear-array (arr) set all elements of arr to 0.0 comb (gen input pm) comb filter comb? (gen) #t if gen is comb filter contrast-enhancement(input (index 1.0)) a kind of phase modulation or companding convolution (sig1 sig2 n) convolve sig1 with sig2 (size n), returning new sig1 convolve (gen input-function) convolve generator convolve? (gen) #t if gen is convolve generator db->linear (db) translate dB value to linear degrees->radians (deg) translate degrees to radians delay (gen input pm) delay line delay is a built-in syntactic form (or whatever they call it) in Scheme, but I don't think this is a case where I care! The name %delay is bound to the original meaning of delay in case you need to use it. delay? (gen) #t if gen is delay line dot-product (sig1 sig2) return dot-product of sig1 with sig2 env (gen) envelope generator env-interp (x env (base 1.0)) return value of env at x env? (gen) #t if gen is env (from make-env) mus-fft (rl im n sign) fft of rl and im (sign = -1 for ifft), result in rl file->array (filename chan start len vct) load len samples of filename into vct starting at frame start in channel chan. file->frame (gen loc frame) return frame from file at loc file->frame? (gen) #t if gen is file->frame generator file->sample (gen loc (chan 0)) return sample from file at loc file->sample? (gen) #t if gen is file->sample generator filter (gen input) filter filter? (gen) #t if gen is filter fir-filter (gen input) FIR filter fir-filter? (gen) #t if gen is fir filter formant (gen input) formant generator formant? (gen) #t if gen is formant generator frame* (fr1 fr2 outfr) element-wise multiply frame+ (fr1 fr2 outfr) element-wise add frame->buffer (buf frame) add frame to buffer frame->file (gen loc frame) write (add) frame to file at loc frame->file? (gen) #t if gen is frame->file generator frame->frame (mixer frame outfr) pass frame through mixer frame-ref (frame chan) return frame[chan] frame->sample (frmix frame) pass frame through frame or mixer to produce sample frame-set! (frame chan val) frame[chan]=val frame? (gen) #t if gen is frame object granulate (gen input-function) granular synthesis generator granulate? (gen) #t if gen is granulate generator hz->radians (freq) translate freq to radians/sample iir-filter (gen input) IIR filter iir-filter? (gen) #t if gen is iir-filter in-any (loc chan stream) return sample in stream at loc and chan in-hz (freq) translate freq to radians/sample ina (loc stream) return sample in stream at loc, chan 0 inb (loc stream) return sample in stream at loc, chan 1 linear->db (val) translate linear val to dB locsig (gen loc input) place input in output channels at loc locsig-ref (gen chan) locsig-scaler[chan] locsig-reverb-ref (gen chan) locsig-reverb-scaler[chan] locsig-set! (gen chan val) locsig-scaler[chan] = val locsig-reverb-set! (gen chan val) locsig-reverb-scaler[chan] = val locsig? (gen) #t if gen is locsig generator ;; all the make function arguments are optional-key args make-all-pass (feedback feedforward size max-size initial-contents initial-element) make-asymmetric-fm (frequency initial-phase r ratio) make-buffer (size fill-time) make-comb (scaler size max-size initial-contents initial-element) make-convolve (input filter fft-size filter-size) make-delay (size initial-contents initial-element max-size) make-env (envelope scaler duration offset base end start) make-fft-window (type size) make-file->frame (name) make-file->sample (name) make-filter (order xcoeffs ycoeffs) make-fir-filter (order xcoeffs) make-formant (radius frequency gain) make-frame (chans &rest vals) make-frame->file (name chans) make-granulate (input expansion length scaler hop ramp jitter max-size) make-iir-filter (order ycoeffs) make-locsig (degree distance reverb output revout channels) make-mixer (chans &rest vals) make-notch (scaler size max-size initial-contents initial-element) make-one-pole (a0 b1) make-one-zero (a0 a1) make-oscil (frequency initial-phase) make-ppolar (radius frequency) make-pulse-train (frequency amplitude initial-phase) make-rand (frequency amplitude) make-rand-interp (frequency amplitude) make-readin (file channel start) make-sample->file (name chans) make-sawtooth-wave (frequency amplitude initial-phase) make-sine-summation (frequency initial-phase n a ratio) make-square-wave (frequency amplitude initial-phase) make-src (input srate width) make-sum-of-cosines (frequency initial-phase cosines) make-table-lookup (frequency initial-phase wave) make-triangle-wave (frequency amplitude initial-phase) make-two-pole (a0 b1 b2) make-two-zero (a0 a1 a2) make-wave-train (frequency initial-phase wave) make-waveshape (frequency partials) make-zpolar (radius frequency) mixer* (mix1 mix2 outmx) matrix multiply of mix1 and mix2 mixer-ref (mix in out) mix-scaler[in,out] mixer-set! (mix in out val) mix-scaler[in,out] = val mixer? (gen) #t if gen is mixer object multiply-arrays (arr1 arr2) arr1[i] *= arr2[i] ;; the "mus-" functions are generic functions, to set use mus-set-var as in mus-set-frequency mus-a0 (gen) a0 field (simple filters) mus-a1 (gen) a1 field (simple filters) mus-a2 (gen) a2 field (simple filters) mus-array-print-length () how many array elements to print in mus_describe mus-b1 (gen) b1 field (simple filters) mus-b2 (gen) b2 field (simple filters) mus-channel (gen) channel of gen mus-channels (gen) channels of gen mus-cosines (gen) cosines of sum-of-cosines gen mus-data (gen) data array of gen mus-feedback (gen) feedback term of gen (simple filters) mus-feedforward (gen) feedforward term of gen (all-pass) mus-formant-radius (gen) formant radius mus-frequency (gen) frequency of gen (Hz) mus-hop (gen) hop amount of gen (granulate) mus-increment (gen) increment of gen (src, readin, granulate) mus-input? (gen) #t if gen is input source mus-length (gen) length of gen mus-location (gen) location (read point) of gen mus-mix (outfile infile (outloc 0) frames (inloc 0) mixer envs) mix infile into outfile starting at outloc in outfile and inloc in infile mixing frames frames of infile. frames defaults to the length of infile. If mixer, use it to scale the various channels; if envs (an array of envelope generators), use it in conjunction with mixer to scale/envelope all the various ins and outs. mus-order (gen) order of gen (filters) mus-output? (gen) #t if gen is output generator mus-phase (gen) phase of gen (radians) mus-ramp (gen) ramp time of gen (granulate) mus-random (val) random numbers bewteen -val and val mus-scaler (gen) scaler of gen mus-set-rand-seed (val) set random number generator seed to val mus-set-srate (val) set sampling rate to val mus-srate () current sampling rate mus-xcoeffs (gen) feedforward (FIR) coeffs of filter mus-ycoeffs (gen) feedback (IIR) coeefs of filter notch (gen input pm) notch filter notch? (gen) #t if gen is notch filter one-pole (gen input) one-pole filter one-pole? (gen) #t if gen is one-pole filter one-zero (gen input) one-zero filter one-zero? (gen) #t if gen is one-zero filter oscil (gen fm pm) sine wave generator oscil? (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-partials->waveshape(partials phases size) create waveshaping table from partials polynomial (coeffs x) evaluate polynomial at x pulse-train (gen fm) pulse-train generator pulse-train? (gen) #t if gen is pulse-train generator radians->degrees (rads) convert radians to degrees radians->hz (rads) convert radians/sample to Hz rand (gen fm) random number generator rand-interp (gen fm) interpolating random number generator rand-interp? (gen) #t if gen is interpolating random number generator rand? (gen) #t if gen is random number generator readin (gen) read one value from associated input stream readin? (gen) #t if gen is readin generator rectangular->polar (rl im) translate from rectangular to polar coordinates restart-env (env) return to start of env ring-modulate (sig1 sig2) sig1 * sig2 (element-wise) sample->buffer (buf samp) store samp in buffer sample->file (gen loc chan val) store val in file at loc in channel chan sample->file? (gen) #t if gen is sample->file generator sample->frame (frmix samp outfr) convert samp to frame sawtooth-wave (gen fm) sawtooth-wave generator sawtooth-wave? (gen) #t if gen is sawtooth-wave generator sine-summation (gen fm) sine-summation generator sine-summation? (gen) #t if gen is sine-summation generator spectrum (rl im win type) produce spectrum of data in rl square-wave (gen fm) square-wave generator square-wave? (gen) #t if gen is square-wave generator src (gen fm input-function) sample rate converter src? (gen) #t if gen is sample-rate converter sum-of-cosines (gen fm) sum-of-cosines (pulse-train) generator sum-of-cosines? (gen) #t if gen is sum-of-cosines generator 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
Here are a few more examples, taken from examp.scm:
(define comb-filter (lambda (scaler size) (let ((cmb (make-comb scaler size))) (lambda (x) (if x (comb cmb x)))))) ; (map-chan (comb-filter .8 32)) ;;; by using filters at harmonically related sizes, we can get chords: (define comb-chord (lambda (scaler size amp) (let ((c1 (make-comb scaler size)) (c2 (make-comb scaler (* size .75))) (c3 (make-comb scaler (* size 1.2)))) (lambda (x) (if x (* amp (+ (comb c1 x) (comb c2 x) (comb c3 x)))))))) ; (map-chan (comb-chord .95 60 .3)) ;;; or change the comb length via an envelope: (define max-envelope (lambda (e mx) (if (null? e) mx (max-envelope (cddr e) (max mx (abs (cadr e))))))) (define zcomb (lambda (scaler size pm) (let ((cmb (make-comb scaler size :max-size (+ size 1 (max-envelope pm 0)))) (penv (make-env :envelope pm :end (frames)))) (lambda (x) (if x (comb cmb x (env penv))))))) ; (map-chan (zcomb .8 32 '(0 0 1 10))) ;;; to impose several formants, just add them in parallel: (define formants (lambda (r1 f1 r2 f2 r3 f3) (let ((fr1 (make-formant r1 f1)) (fr2 (make-formant r2 f2)) (fr3 (make-formant r3 f3))) (lambda (x) (if x (+ (formant fr1 x) (formant fr2 x) (formant fr3 x))))))) ; (map-chan (formants .01 900 .02 1800 .01 2700)) ;;; to get a moving formant: (define moving-formant (lambda (radius move) (let ((frm (make-formant radius (cadr move))) (menv (make-env :envelope move :end (frames)))) (lambda (x) (if x (let ((val (formant frm x))) (mus-set-frequency frm (env menv)) val)))))) ; (map-chan (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)) (inctr 0) (out-data (make-vct len))) (do ((i 0 (1+ i))) ((= i len)) (vct-set! out-data i (src sr (* osamp (oscil os)) (lambda (dir) (let ((val (sample inctr))) (set! inctr (+ inctr dir)) val))))) (vct->samples 0 len out-data)))) ; (fp 1.0 .3 20) ;;; -------- shift pitch keeping duration constant ;;; ;;; both src and granulate take a function argument to get input whenever it is needed. ;;; in this case, src calls granulate which reads the currently selected file. (define expsrc (lambda (rate) (let* ((gr (make-granulate :expansion rate)) (sr (make-src :srate rate)) (inctr 0)) (lambda (inval) (if inval (src sr 0.0 (lambda (dir) (granulate gr (lambda (dir) (let ((val (sample inctr))) (set! inctr (+ inctr dir)) val))))))))))
Geez, I haven't had this much fun in a long time! Check out examp.scm for more.
It is possible to add your own user-interface elements controlling your plug-ins. As a very simple example, let's add a dialog window with a slider to the scale plug-in given above.
/* scale.c, includes dialog-window with slider to set scale value */ #include "snd.h" static int sample_ready = 0, current_sample = 0; static Widget scale_dialog = NULL; /* this will hold our slider */ static float current_scaler = 1.0; static void Help_Scale_Callback(Widget w,XtPointer clientData,XtPointer callData) { fprintf(stderr,"move the slider to affect the volume"); } static void Dismiss_Scale_Callback(Widget w,XtPointer clientData,XtPointer callData) { XtUnmanageChild(scale_dialog); } static void Scale_Callback(Widget w,XtPointer clientData,XtPointer callData) { XmScaleCallbackStruct *cb = (XmScaleCallbackStruct *)callData; current_scaler = (float)(cb->value/100.0); } static int create_scale_dialog(snd_state *ss) { Arg args[32]; int n,i; XmString xhelp,xdismiss,titlestr; Widget mainform,scale; if (!scale_dialog) { xdismiss = XmStringCreate("Dismiss",XmFONTLIST_DEFAULT_TAG); xhelp = XmStringCreate("Help",XmFONTLIST_DEFAULT_TAG); titlestr = XmStringCreate("Scaling Plug-in",XmFONTLIST_DEFAULT_TAG); n=0; XtSetArg(args[n],XmNcancelLabelString,xdismiss); n++; XtSetArg(args[n],XmNhelpLabelString,xhelp); n++; XtSetArg(args[n],XmNautoUnmanage,FALSE); n++; XtSetArg(args[n],XmNdialogTitle,titlestr); n++; XtSetArg(args[n],XmNresizePolicy,XmRESIZE_GROW); n++; XtSetArg(args[n],XmNnoResize,FALSE); n++; XtSetArg(args[n],XmNtransient,FALSE); n++; scale_dialog = XmCreateTemplateDialog(ss->sgx->mainshell,"Scaling Plug-in",args,n); XtAddCallback(scale_dialog,XmNcancelCallback,Dismiss_Scale_Callback,ss); XtAddCallback(scale_dialog,XmNhelpCallback,Help_Scale_Callback,ss); XmStringFree(xhelp); XmStringFree(xdismiss); XmStringFree(titlestr); n=0; XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++; XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++; XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++; XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++; XtSetArg(args[n],XmNbottomWidget,XmMessageBoxGetChild(scale_dialog,XmDIALOG_SEPARATOR)); n++; mainform = XtCreateManagedWidget("formd",xmFormWidgetClass,scale_dialog,args,n); n=0; XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++; XtSetArg(args[n],XmNshowValue,TRUE); n++; XtSetArg(args[n],XmNvalue,100); n++; XtSetArg(args[n],XmNmaximum,500); n++; XtSetArg(args[n],XmNdecimalPoints,2); n++; scale = XtCreateManagedWidget("",xmScaleWidgetClass,mainform,args,n); XtAddCallback(scale,XmNvalueChangedCallback,Scale_Callback,ss); XtAddCallback(scale,XmNdragCallback,Scale_Callback,ss); } XtManageChild(scale_dialog); return(PLUG_OK); } /* the rest is as before except that we add create_scale_dialog to the plug-in struct */ static int send_sample(int *new_value) { if (sample_ready) { (*new_value) = current_sample; sample_ready = 0; return(PLUG_OK); } else return(PLUG_NO_DATA); } static int receive_sample(int value, int last_value) { sample_ready = 1; current_sample = value * current_scaler; /* this is our scaling operation */ return(PLUG_OK); } static snd_plug *plug = NULL; SCM snd_scale(void) { if (plug == NULL) { plug = (snd_plug *)calloc(1,sizeof(snd_plug)); plug->init = create_scale_dialog; plug->quit = NULL; plug->start_channel = NULL; plug->end_channel = NULL; plug->read_sample = receive_sample; plug->write_sample = send_sample; plug->name = "scale"; plug->documentation = "scales by 2"; plug->edit_name = "(snd-scale)"; scm_sysintern("scale-plug",gh_ulong2scm((unsigned long)plug)); } gh_eval_str("(call-plug scale-plug)"); return(SCM_BOOL_F); } void init_snd_scale(void) { gh_new_procedure("snd-scale",snd_scale,0,0,0); } /* end of scale.c */