]> git.donarmstrong.com Git - lib.git/blob - emacs_el/ecasound.el
Initial user home directory commit
[lib.git] / emacs_el / ecasound.el
1 ;;; ecasound.el --- Interactive and programmatic interface to Ecasound
2
3 ;; Copyright (C) 2001, 2002  Mario Lang
4
5 ;; Author: Mario Lang <mlang@delysid.org>
6 ;; Keywords: audio, ecasound, eci, comint, process, pcomplete
7 ;; Version: 0.8.2
8
9 ;; This file is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; This file is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to
21 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;; This file implements several aspects of ecasound use:
27 ;;
28 ;; * A derived-major-mode, from comint mode for an inferior ecasound
29 ;; process (ecasound-aim-mode).  Complete with context sensitive
30 ;; completion and interactive features to control the current process
31 ;; using ECI.
32 ;;
33 ;; * Ecasound Control Interface (ECI) library for programmatic control
34 ;; of a Ecasound process.  This allows you to write Ecasound batch
35 ;; jobs in Emacs-Lisp with Lisp functions and return values.  Have a
36 ;; look at eci-example and ecasound-normalize.
37 ;;
38 ;; * ecasound-ewf-mode, a mode for editing .ewf files.
39 ;;
40 ;;
41 ;; Usage:
42 ;;
43 ;; You need at least ecasound 2.2.0 for this file to work properly.
44 ;;
45 ;; Put ecasound.el in your load-path and require it in your .emacs.
46 ;; Set `ecasound-program' to the path to your ecasound executable.
47 ;;
48 ;;  (setq load-path (cons "/home/user/elisp")
49 ;;  (require 'ecasound)
50 ;;  (setq ecasound-program "/home/user/bin/ecasound"
51 ;;        eci-program "/home/user/bin/ecasound")
52 ;;
53 ;; To set ecasound startup options use
54 ;;
55 ;;  M-x ecasound-customize-startup RET
56 ;;
57 ;; Then use M-x ecasound RET to invoke an inferior ecasound process.
58 ;;
59 ;; For programmatic use of the ECI API, have a look at `eci-init',
60 ;; `eci-command' and in general the eci-* namespace.
61 ;;
62 ;; Compatibility:
63 ;;
64 ;; This file only works with GNU Emacs 21.  I've invested some minimal efforts
65 ;; to get it working with XEmacs, but have so far failed to succeed.
66 ;; Motivation isn't very high to get it working with XEmacs since I personally
67 ;; never use it.  So if you would like to use ecasound.el under XEmacs, you
68 ;; will have ttodo
69 ;;  M-x toggle-debug-on-error RET
70 ;; and see what you can figure out.  I'm happy to receive useful suggestions.
71 ;;
72 ;; Todo:
73 ;;
74 ;; * Find a better way to do status info fetching...
75 ;; * Add more conditions to the menu.
76 ;; * Use map-xxx-list data in the ecasound-copp widget.  This means we
77 ;;   need to merge cop-status and map-cop-list data somehow or have
78 ;;   the cop-editor fetch hints from map-cop/ladpsa/preset-list.
79 ;; * Make `ecasound-signalview' faster, and allow to invoke it on already
80 ;;   opened sessions.
81 ;; * Fix the case where ecasound sends output *after* the prompt.
82 ;;   This is tricky!  Fixed for internal parsing, probably will leave
83 ;;   like that for interactive use, not worth the trouble...
84 ;; * Copy documentation for ECI commands into eci-* docstrings and menu
85 ;;   :help keywords.
86 ;; * Expand the menu.
87 ;; * Bind most important interactive functions in ecasound-iam-mode-map
88 ;;   (which layout to use?)
89
90 ;;; History:
91 ;; 
92 ;; Version: 0.8.2
93 ;;
94 ;; * Added quite some missing docstrings.
95 ;; * New variable `ecasound-last-command-alist'.  Use that to do fancy stuff
96 ;;   to certain commands return values.
97 ;; * New variable `ecasound-type-alist'.  Normally you should not need to
98 ;;   change this, but it's nice to have it configurable.
99 ;; * New function `eci-is-valid-p'.  Rationale is that nil as return
100 ;;   value of a ECI command should indicate an error.  So this function
101 ;;   with a -p suffix to use as a predicate.
102 ;; * New variable `ecasound-parent' holds the parent buffer in a daemon buffer.
103 ;; * New variables ecasound-timer-flag&interval.
104 ;; * Renamed `eci-output-filter' to `ecasound-output-filter'.
105 ;; * New variable ecasound-mode|header-line-format.
106 ;; * `ecasound-cop-edit' now uses cop-set instead of
107 ;;   cop-select+copp-select+copp-set to update values.
108 ;; * Fixed multiple-argument handling.   They are separated with ',', not
109 ;;   with a space.
110 ;; * New variable ecasound-sending-command, used to prevent the background
111 ;;   timer from coliding with other ECI requests.
112 ;;
113 ;; Version: 0.8.1
114 ;;
115 ;; * Make ai|ao|cs-forward|rewind use ai|ao|cs-selected in the prompt
116 ;;   string of the interactive spec.
117 ;; * New keymaps ecasound-audioin|audioout-map.
118 ;;   Now you can be very quick:
119 ;;  M-x ecasound RET M-i a <select file> RET M-o d start RET
120 ;; * New menu ecasound-iam-ai|ao-menu.
121 ;; * defeci for ai|ao-add|forward|iselect|list|rewind|select|selected
122 ;; * Deleted `ecasound-buffer-name' and `eci-buffer-name' and replaced
123 ;;   calls to `make-comint-in-buffer' with `make-comint'.
124 ;; * Extended defeci's :cache and :cache-doc to defvar the variable.
125 ;; * Cleaned up some old alias definitions.
126 ;;
127 ;; Version: 0.8.0
128 ;;
129 ;; * New custom type ecasound-args, which is now used for `ecasound-arguments'
130 ;;   and `eci-arguments'.
131 ;; * If :cache is specified, also try to find a cached version in daemon-buffer
132 ;;   if available.
133 ;; * Added :alias keyword to defeci.  Delete defecialias.
134 ;; * Added ":pcomplete doc" to several defeci calls.
135 ;; * ecasound-cop|ctrl-add deleted and merged with the interactive spec of
136 ;;   eci-cop|ctrl-add.  Now if prefix arg (C-u) is given, prompt for plain
137 ;;   string, otherwise prompt with completion. Also changed binding
138 ;;   in ChainOp menu.
139 ;; * `ecasound-messages': variable deleted.
140 ;; * `ecasound-arguments': Now handles -d:nnn properly.
141 ;; * Many other minor tweaks and fixes.
142 ;;
143 ;; Version: 0.7.9
144 ;;
145 ;; * Cleanup and extend `defeci', now handles keyword :cache and :pcomplete.
146 ;;   Lots of `defeci'-caller updates, and additions.
147 ;; * Extended `ecasound-arguments' customize defition to handle --daemon,
148 ;; --daemon-port:nnn, -n:name and -b:size.  New interactive function
149 ;; `ecasound-customize-startup', also bound in "Ecasound menu."
150 ;; * Added status-information fetching via timer-function.  Puts
151 ;; info in mode-line as well as header-line. (warning, this feature is still
152 ;; a bit unstable.)
153 ;; * New macro `eci-hide-output' used to redirect action to `ecasound-daemon'
154 ;; if possible.  Several completion-fascilities updated to use it.
155 ;; * Various other fixes.
156 ;;
157 ;; Version: 0.7.8
158 ;;
159 ;; * Fix bug in "cop-add -el:" completion.
160 ;; * Made `ecasound-format-arg' a bit prettier.
161 ;; * Add --daemon support.  If --daemon is set in `ecasound-arguments',
162 ;; ecasound-iam-mode will take advantage of that and initialize a
163 ;; `ecasound-daemon' channel, as well as a periodic timer to update the
164 ;; mode-line.  M-: (display-buffer ecasound-daemon) RET to view its contents.
165 ;;
166 ;; Version: 0.7.7
167 ;;
168 ;; * Fixed hangup if a Stringlist ('S') returned a empty list.
169 ;; * Added keybindings.  See C-h m for details.  Still alot missing.
170 ;; * Added cs-forward and cs-rewind.  Can be used interactively, or
171 ;; prompt for value.  With no prefix arg, prompts for value, with
172 ;; prefix arg, uses that.  Example: C-u M-c M-s f forwards the chainsetup
173 ;; by 4 seconds, M-9 M-c M-s f forwards nine seconds ...
174 ;; * Fixed field-no-longer-editable bug when +/- is used in
175 ;; ecasound-cop-editor (thanks Per).  This also makes the slider useful again.
176 ;; * Got rid of ecasound-prompt assumptions in `eci-parse' and `eci-command'.
177 ;; * Make the eci-command family work with --daemon tcp/ip connections.
178 ;;   (no code for initialising daemon stuff yet, but eci-* commands
179 ;;    work fine with tcp/ip conns (tested manually).
180 ;; * `eci-parse' deleted and merged with `eci-output-filter'.
181 ;;
182 ;; Version: 0.7.6
183 ;;
184 ;; * Various minor bugfixes and enhancements.
185 ;; * Implemented ecasignalview as `ecasound-signalview' directly in Lisp.
186 ;; This is another demonstration that ECI was really a Good Thing(tm)!
187 ;; * Changed defeci to make it look more like a defun.
188 ;; * Removed eci-process-*-register handling completely. Rationale is
189 ;; that the map-*-list stuff is actually much more uniform and offers more
190 ;; info.
191 ;; * Rewrote `pcomplete/ecasound-iam-mode/cop-add' to use map-*-list.
192 ;; * Rewrote `ecasound-ctrl-add' using map-ctrl-list instead of ctrl-register
193 ;; and `ecasound-read-copp'.
194 ;; * Rewrote `ecasound-cop-add' using map-cop|ladspa|preset-list.
195 ;; * New function `eci-process-map-list' which processes the new map-xxx-list
196 ;; output into a wellformed Lisp list.
197 ;; * `ecasound-iam-commands' is now filled using int-cmd-list.
198 ;; * cop-map-list handling.  Used in `ecasound-cop-add' now.  New function
199 ;; `ecasound-read-copp' uses the now available default value.
200 ;;
201 ;; Version: 0.7.5
202 ;;
203 ;; * Added ctrl-register parsing support and `ecasound-ctrl-add'.
204 ;; * Added preset-register support (so far only for cop-add completion)
205 ;; * Fixed cop-status parsing bug which caused `ecasound-cop-edit' to not
206 ;; work in some cases.
207 ;; * New macro defeci which handles defining ECI commands in lisp.
208 ;; * Several other minor tweaks and fixes.
209 ;;
210 ;; Version: 0.7.4
211 ;;
212 ;; * Fixed `eci-command' once again, it blocked for nearly every call... :(
213 ;; * Fixed ecasound-cop-add in the ladspa case.
214 ;;
215 ;; Version: 0.7.3
216 ;;
217 ;; * Fixed missing require.
218 ;;
219 ;; Version: 0.7.2
220 ;;
221 ;; * Integrated ladspa-register into ecasound-cop-add
222 ;; Now we've a very huge list to select from using completion.
223 ;; * Some little cleanups.
224 ;; * Fixed ecasound-cop-add to actually add the ':' between name and args.
225 ;; * Removed the slider widget for now from the :format property of
226 ;; ecasound-copp.
227 ;; * Added `ecasound-messages' for a nice customisable interface to
228 ;; loglevels, strangely, cvs version doesnt seem to recognize
229 ;; -d:%d
230 ;;
231 ;; Version: 0.7.1
232 ;;
233 ;; * Created a slider widget.  It's not flawless, but it works!
234 ;;
235
236 ;;; Code:
237
238 (require 'cl)
239 (require 'comint)
240 (require 'easymenu)
241 (require 'pcomplete)
242 (require 'widget)
243 (require 'wid-edit)
244
245 (defgroup ecasound nil
246   "Ecasound is a software package designed for multitrack audio processing.
247 It can be used for simple tasks like audio playback, recording and format
248 conversions, as well as for multitrack effect processing, mixing, recording
249 and signal recycling.  Ecasound supports a wide range of audio inputs, outputs
250 and effect algorithms.  Effects and audio objects can be combined in various
251 ways, and their parameters can be controlled by operator objects like
252 oscillators and MIDI-CCs.
253
254 Variables in this group affect inferior ecasound processes started from
255 within Emacs using the command `ecasound'.
256
257 See the subgroup `eci' for settings which affect the programmatic interface
258 to ECI."
259   :prefix "ecasound-"
260   :group 'processes)
261
262 (define-widget 'ecasound-cli-arg 'string
263   "A Custom Widget for a command-line argument."
264   :format "%t: %v%d"
265   :string-match 'ecasound-cli-arg-string-match
266   :match 'ecasound-cli-arg-match
267   :value-to-internal
268   (lambda (widget value)
269     (when (widget-apply widget :string-match value)
270       (match-string 1 value)))
271   :value-to-external
272   (lambda (widget value)
273     (format (widget-apply widget :arg-format) value)))
274
275 (defun ecasound-cli-arg-match (widget value)
276   (when (stringp value)
277     (widget-apply widget :string-match value)))
278
279 (defun ecasound-cli-arg-string-match (widget value)
280   (string-match
281    (format (concat "^" (regexp-quote (widget-get widget :arg-format)))
282            (concat "\\(" (widget-get widget :pattern) "\\)"))
283    value))
284
285 (define-widget 'ecasound-daemon-port 'ecasound-cli-arg
286   "A Custom Widget for the --daemon-port:port argument."
287   :pattern ".*"
288   :arg-format "--daemon-port:%s")
289
290 (define-widget 'ecasound-chainsetup-name 'ecasound-cli-arg
291   "A Custom Widget for the -n:chainsetup argument."
292   :arg-format "-n:%s"
293   :doc "Sets the name of chainsetup.
294 If not specified, defaults either to \"command-line-setup\" or to the file
295 name from which chainsetup was loaded.  Whitespaces are not allowed."
296   :format "%t: %v%h"
297   :pattern ".*"
298   :tag "Chainsetup name")
299
300 (define-widget 'ecasound-buffer-size 'ecasound-cli-arg
301   "A Custom Widget for the -b:buffer size argument."
302   :arg-format "-b:%s"
303   :doc "Sets the size of buffer in samples (must be an exponent of 2).
304 This is quite an important option. For real-time processing, you should set
305 this as low as possible to reduce the processing delay.  Some machines can
306 handle buffer values as low as 64 and 128.  In some circumstances (for
307 instance when using oscillator envelopes) small buffer sizes will make
308 envelopes act more smoothly.  When not processing in real-time (all inputs
309 and outputs are normal files), values between 512 - 4096 often give better
310 results."
311   :format "%t: %v%h"
312   :pattern "[0-9]+"
313   :tag "Buffer size")
314
315 (define-widget 'ecasound-debug-level 'set
316   "Custom widget for the -d:nnn argument."
317   :arg-format "-d:%s"
318   :args '((const :tag "Errors" 1)
319           (const :tag "Info" 2)
320           (const :tag "Subsystems" 4)
321           (const :tag "Module names" 8)
322           (const :tag "User objects" 16)
323           (const :tag "System objects" 32)
324           (const :tag "Functions" 64)
325           (const :tag "Continuous" 128)
326           (const :tag "EIAM return values" 256))
327   :doc "Set the debug level"
328   :match 'ecasound-cli-arg-match
329   :pattern "[0-9]+"
330   :string-match 'ecasound-cli-arg-string-match
331   :tag "Debug level"
332   :value-to-external
333   (lambda (widget value)
334     (format (widget-get widget :arg-format)
335             (number-to-string (apply #'+ (widget-apply widget :value-get)))))
336   :value-to-internal
337   (lambda (widget value)
338     (when (widget-apply widget :string-match value)
339       (let ((level (string-to-number (match-string 1 value)))
340             (levels (nreverse
341                      (mapcar (lambda (elt) (car (last elt)))
342                              (widget-get widget :args)))))
343         (if (or (> level (apply #'+ levels)) (< level 0))
344             (error "Invalid debug level %d" level)
345           (delq nil
346                 (mapcar (lambda (elem)
347                           (when (eq (/ level elem) 1)
348                             (setq level (- level elem))
349                             elem)) levels)))))))
350  
351 (define-widget 'ecasound-args 'set
352   ""
353   :args '((const :tag "Start ecasound in interactive mode" "-c")
354           (const :tag "Print all debug information to stderr"
355                  :doc "(unbuffered, plain output without ncurses)"
356                  "-D")
357           (ecasound-debug-level)
358           (list :format "%v" :inline t
359                 (const :tag "Allow remote connections:" "--daemon")
360                 (ecasound-daemon-port :tag "Daemon port" "--daemon-port:2868"))
361           (ecasound-buffer-size "-b:1024")
362           (ecasound-chainsetup-name "-n:eca-el-setup")
363           (const :tag "Truncate outputs" :format "%t\n%h"
364                      :doc "All output objects are opened in overwrite mode.
365 Any existing files will be truncated."
366                      "-x")
367               (const :tag "Open outputs for updating"
368                      :doc "Ecasound opens all outputs - if target format allows it - in readwrite mode."
369                      "-X")
370               (repeat :tag "Others" :inline t (string :tag "Argument"))))
371
372 (defcustom ecasound-arguments '("-c" "-d:259" "--daemon" "--daemon-port:2868"
373                                 "-n:eca-el-setup")
374   "*Command line arguments used when starting an ecasound process."
375   :group 'ecasound
376   :type 'ecasound-args)
377
378 (defun ecasound-daemon-port ()
379   "Return the port number defined in `ecasound-arguments'."
380   (let ((elem (member* "^--daemon-port:\\(.*\\)" ecasound-arguments
381                        :test #'string-match)))
382     (if elem
383         (match-string 1 (car elem)))))
384
385 (defun ecasound-customize-startup ()
386   "Customize ecasound startup arguments."
387   (interactive)
388   (customize-variable 'ecasound-arguments))
389
390 (defcustom ecasound-program "/home/mlang/bin/ecasound"
391   "*Ecasound's executable.
392 This program is executed when the user invokes \\[ecasound]."
393   :group 'ecasound
394   :type 'file)
395
396 (defcustom ecasound-prompt-regexp "^ecasound[^>]*> "
397   "Regexp to use to match the prompt."
398   :group 'ecasound
399   :type 'regexp)
400
401 (defcustom ecasound-parse-cleanup-buffer t
402   "*Indicates if `ecasound-output-filter' should cleanup the buffer.
403 This means the loglevel, msgsize and return type will get removed if
404 parsed successfully."
405   :group 'ecasound
406   :type 'boolean)
407
408 (defcustom ecasound-error-hook nil
409   "*Called whenever a ECI error happens."
410   :group 'ecasound
411   :type 'hook)
412
413 (defcustom ecasound-message-hook '(ecasound-print-message)
414   "*Hook called whenever a message except loglevel 256 (eci) is received.
415 Arguments are LOGLEVEL and STRING."
416   :group 'ecasound
417   :type 'hook)
418
419 (defun ecasound-print-message (level msg)
420   "Simple function which prints every message regardless which loglevel.
421 Argument LEVEL is the debug level."
422   (message "Ecasound (%d): %s" level msg))
423
424 (defface ecasound-error-face '((t (:foreground "White" :background "Red")))
425   "Face used to highlight errors."
426   :group 'ecasound)
427
428 (defcustom ecasound-timer-flag t
429   "*If non-nil, fetch status information in background."
430   :group 'ecasound
431   :type 'boolean)
432
433 (defcustom ecasound-timer-interval 2
434   "*Defines how often status information should be fetched."
435   :group 'ecasound
436   :type 'number)
437
438 (defcustom ecasound-mode-line-format
439   '("-"
440     mode-line-frame-identification
441     mode-line-buffer-identification
442     eci-engine-status " "
443     ecasound-mode-string
444     " %[("
445     (:eval (mode-line-mode-name))
446     mode-line-process
447     minor-mode-alist
448     "%n"
449     ")%]--"
450    (line-number-mode "L%l--")
451    (column-number-mode "C%c--")
452    (-3 . "%p")
453    "-%-")
454   "*Mode Line Format used in `ecasound-iam-mode'."
455   :group 'ecasound
456   :type '(repeat
457           (choice
458            string
459            variable
460            (cons integer string)
461            (list :tag "Evaluate" (const :value :eval) sexp)
462            (repeat sexp))))
463
464 (defcustom ecasound-header-line-format nil
465   "*If non-nil, defines the header line format for `ecasound-iam-mode' buffers."
466   :group 'ecasound
467   :type 'sexp)
468
469 (defvar ecasound-sending-command nil
470   "Non-nil if `eci-command' is running.")
471
472 (make-variable-buffer-local
473  (defvar ecasound-daemon nil
474    "If non-nil, this variable holds the buffer object of a daemon channel."))
475
476 (make-variable-buffer-local
477  (defvar ecasound-parent nil
478    "If non-nil, this variable holds the buffer object of a daemon parent."))
479
480 (make-variable-buffer-local
481  (defvar ecasound-daemon-timer nil))
482
483 (defvar ecasound-chain-map nil
484   "Keymap used for Chain operations.")
485 (define-prefix-command 'ecasound-chain-map)
486 (define-key 'ecasound-chain-map "a" 'eci-c-add)
487 (define-key 'ecasound-chain-map "c" 'eci-c-clear)
488 (define-key 'ecasound-chain-map "d" 'eci-c-deselect)
489 (define-key 'ecasound-chain-map "m" 'eci-c-mute)
490 (define-key 'ecasound-chain-map "x" 'eci-c-remove)
491 (define-key 'ecasound-chain-map (kbd "M-s") 'ecasound-cs-map)
492 (define-key 'ecasound-chain-map (kbd "M-o") 'ecasound-cop-map)
493 (defvar ecasound-cop-map nil
494   "Keymap used for Chain operator operations.")
495 (define-prefix-command 'ecasound-cop-map)
496 (define-key 'ecasound-cop-map "a" 'eci-cop-add)
497 (define-key 'ecasound-cop-map "i" 'eci-cop-select)
498 (define-key 'ecasound-cop-map "l" 'eci-cop-list)
499 (define-key 'ecasound-cop-map "s" 'eci-cop-status)
500 (define-key 'ecasound-cop-map "x" 'eci-cop-remove)
501 (defvar ecasound-audioin-map nil
502   "Keymap used for audio input objects.")
503 (define-prefix-command 'ecasound-audioin-map)
504 (define-key 'ecasound-audioin-map "a" 'eci-ai-add)
505 (define-key 'ecasound-audioin-map "f" 'eci-ai-forward)
506 (define-key 'ecasound-audioin-map "r" 'eci-ai-rewind)
507 (define-key 'ecasound-audioin-map "x" 'eci-ai-remove)
508 (defvar ecasound-audioout-map nil
509   "Keymap used for audio output objects.")
510 (define-prefix-command 'ecasound-audioout-map)
511 (define-key 'ecasound-audioout-map "a" 'eci-ao-add)
512 (define-key 'ecasound-audioout-map "d" 'eci-ao-add-default)
513 (define-key 'ecasound-audioout-map "f" 'eci-ao-forward)
514 (define-key 'ecasound-audioout-map "r" 'eci-ao-rewind)
515 (define-key 'ecasound-audioout-map "x" 'eci-ao-remove)
516 (defvar ecasound-cs-map nil
517   "Keymap used for Chainsetup operations.")
518 (define-prefix-command 'ecasound-cs-map)
519 (define-key 'ecasound-cs-map "a" 'eci-cs-add)
520 (define-key 'ecasound-cs-map "c" 'eci-cs-connect)
521 (define-key 'ecasound-cs-map "d" 'eci-cs-disconnect)
522 (define-key 'ecasound-cs-map "f" 'eci-cs-forward)
523 (define-key 'ecasound-cs-map "r" 'eci-cs-rewind)
524 (define-key 'ecasound-cs-map "t" 'eci-cs-toogle-loop)
525
526 (defvar ecasound-iam-mode-map
527   (let ((map (make-sparse-keymap)))
528     (set-keymap-parent map comint-mode-map)
529     (define-key map "\t" 'pcomplete)
530     (define-key map (kbd "M-c") 'ecasound-chain-map)
531     (define-key map (kbd "M-i") 'ecasound-audioin-map)
532     (define-key map (kbd "M-o") 'ecasound-audioout-map)
533     (define-key map (kbd "M-\"") 'eci-command)
534     map))
535
536 (easy-menu-define
537   ecasound-iam-cs-menu ecasound-iam-mode-map
538   "Chainsetup menu."
539   (list "Chainsetup"
540         ["Add..." eci-cs-add t]
541         ["Load..." eci-cs-load t]
542         ["Save" eci-cs-save t]
543         ["Save As..." eci-cs-save-as t]
544         ["List" eci-cs-list t]
545         ["Select" eci-cs-select t]
546         ["Select via index" eci-cs-index-select t]
547         "-"
548         ["Selected" eci-cs-selected t]
549         ["Valid?" eci-cs-is-valid t]
550         ["Connect" eci-cs-connect (eci-cs-is-valid-p)]
551         ["Disconnect" eci-cs-disconnect t]
552         ["Get position" eci-cs-get-position t]
553         ["Get length" eci-cs-get-length t]
554         ["Get length in samples" eci-cs-get-length-samples t]
555         ["Forward..." eci-cs-forward t]
556         ["Rewind..." eci-cs-rewind t]
557         ))
558 (easy-menu-add ecasound-iam-cs-menu ecasound-iam-mode-map)
559 (easy-menu-define
560   ecasound-iam-c-menu ecasound-iam-mode-map
561   "Chain menu."
562   (list "Chain"
563         ["Add..." eci-c-add t]
564         ["Select..." eci-c-select t]
565         ["Select All" eci-c-select-all t]
566         ["Deselect..." eci-c-deselect (> (length (eci-c-selected)) 0)]
567         ["Selected" eci-c-selected t]
568         ["Mute" eci-c-mute t]
569         ["Clear" eci-c-clear t]
570         ))
571 (easy-menu-add ecasound-iam-c-menu ecasound-iam-mode-map)
572 (easy-menu-define
573   ecasound-iam-cop-menu ecasound-iam-mode-map
574   "Chain Operator menu."
575   (list "ChainOp"
576         ["Add..." eci-cop-add (> (length (eci-c-selected)) 0)]
577         ["Select..." eci-cop-select t]
578         ["Edit..." ecasound-cop-edit t]
579         "-"
580         ["Select parameter..." eci-copp-select t]
581         ["Get parameter value" eci-copp-get t]
582         ["Set parameter value..." eci-copp-set t]
583         ))
584 (easy-menu-add ecasound-iam-c-menu ecasound-iam-mode-map)
585 (easy-menu-define
586   ecasound-iam-ai-menu ecasound-iam-mode-map
587   "Audio Input Object menu."
588   (list "AudioIn"
589         ["Add..." eci-ai-add (> (length (eci-c-selected)) 0)]
590         ["List" eci-ai-list t]
591         ["Select..." eci-ai-select t]
592         ["Index select..." eci-ai-index-select t]
593         "-"
594         ["Attach" eci-ai-attach t]
595         ["Remove" eci-ai-remove t]
596         ["Forward..." eci-ai-forward t]
597         ["Rewind..." eci-ai-rewind t]
598         ))
599 (easy-menu-add ecasound-iam-ai-menu ecasound-iam-mode-map)
600 (easy-menu-define
601   ecasound-iam-ao-menu ecasound-iam-mode-map
602   "Audio Output Object menu."
603   (list "AudioOut"
604         ["Add..." eci-ao-add (> (length (eci-c-selected)) 0)]
605         ["Add default" eci-ao-add-default (> (length (eci-c-selected)) 0)]
606         ["List" eci-ao-list t]
607         ["Select..." eci-ao-select t]
608         ["Index select..." eci-ao-index-select t]
609         "-"
610         ["Attach" eci-ao-attach t]
611         ["Remove" eci-ao-remove t]
612         ["Forward..." eci-ao-forward t]
613         ["Rewind..." eci-ao-rewind t]
614         ))
615 (easy-menu-add ecasound-iam-ao-menu ecasound-iam-mode-map)
616
617 (easy-menu-define
618   ecasound-menu global-map
619   "Ecasound menu."
620   (list "Ecasound"
621         ["Get session" ecasound t]
622         "-"
623         ["Normalize..." ecasound-normalize t]
624         ["Signalview..." ecasound-signalview t]
625         "-"
626         ["Customize startup..." ecasound-customize-startup t]
627         ))
628 (easy-menu-add ecasound-menu global-map)
629
630 (make-variable-buffer-local
631  (defvar ecasound-mode-string nil))
632
633 (define-derived-mode ecasound-iam-mode comint-mode "EIAM"
634   "Special mode for ecasound processes in interactive mode."
635   (set (make-local-variable 'comint-prompt-regexp)
636        (set (make-local-variable 'paragraph-start)
637             ecasound-prompt-regexp))
638   (add-hook 'comint-output-filter-functions 'ecasound-output-filter nil t)
639   (add-hook 'comint-input-filter-functions 'eci-input-filter nil t)
640   (ecasound-iam-setup-pcomplete)
641   (setq mode-line-format ecasound-mode-line-format))
642
643 (defun ecasound-mode-line-cop-list (handle)
644   (let ((list (eci-cop-list handle))
645         (sel (1- (eci-cop-selected handle)))
646         (str ""))
647     (dotimes (i (length list) str)
648       (setq str (format "%s%s%s%s"
649                         str
650                         (if (= i sel) "*" "")
651                         (nth i list)
652                         (if (= i (length list)) "" ","))))))
653
654 (defsubst ecasound-daemon-p ()
655   "Predicate used to determine if there is an active daemon channel."
656   (and (buffer-live-p ecasound-daemon)
657        (eq (process-status ecasound-daemon) 'open)))
658
659 (defun ecasound-kill-timer ()
660   "Cancels the background timer.
661 Use this if you want to stop background information fetching."
662   (interactive)
663   (when ecasound-daemon-timer
664     (cancel-timer ecasound-daemon-timer)))
665
666 (defun ecasound-kill-daemon ()
667   "Terminate the daemon channel."
668   (interactive)
669   (ecasound-kill-timer)
670   (when (ecasound-daemon-p)
671     (kill-buffer ecasound-daemon)))
672
673 (defun ecasound-update-mode-line (buffer)
674   (when (and (buffer-live-p buffer)
675              (get-buffer-window buffer 'visible))
676     (unless ecasound-sending-command
677       (with-current-buffer buffer
678         (when (ecasound-daemon-p)
679           (eci-engine-status ecasound-daemon)
680           (setq ecasound-mode-string
681                 (list
682                  " [" (ecasound-position-to-string
683                        (eci-cs-get-position ecasound-daemon))
684                  "/" (ecasound-position-to-string
685                       (eci-cs-get-length ecasound-daemon))
686                  "]"
687                  )
688                 header-line-format
689                 (list
690                  (eci-cs-selected ecasound-daemon)
691                  " [" (if (eci-cs-is-valid-p ecasound-daemon)
692                           "valid"
693                         "N/A") "]: ("
694                  (mapconcat 'identity (eci-c-list ecasound-daemon) ",")
695                  ") "
696                  (mapconcat 'identity
697                             (eci-c-selected ecasound-daemon) ","))))))))
698
699 (defun ecasound-setup-timer ()
700   (when (and ecasound-timer-flag (ecasound-daemon-p))
701     (setq ecasound-daemon-timer
702           (run-with-timer
703            0 ecasound-timer-interval
704            'ecasound-update-mode-line
705            (current-buffer)))))
706
707 (make-variable-buffer-local
708  (defvar eci-int-output-mode-wellformed-flag nil
709    "Indicates if int-output-mode-wellformed was successfully initialized."))
710
711 (make-variable-buffer-local
712  (defvar eci-engine-status nil
713    "If non-nil, a string describing the engine-status."))
714
715 (make-variable-buffer-local
716  (defvar eci-cs-selected nil
717    "If non-nil, a string describing the selected chain setup."))
718
719 ;;;###autoload
720 (defun ecasound (&optional buffer)
721   "Run an inferior ecasound, with I/O through BUFFER.
722 BUFFER defaults to `*ecasound*'.
723 Interactively, a prefix arg means to prompt for BUFFER.
724 If BUFFER exists but ecasound process is not running, make new ecasound
725 process using `ecasound-arguments'.
726 If BUFFER exists and ecasound process is running, just switch to BUFFER.
727 The buffer is put in ecasound mode, giving commands for sending input and
728 completing IAM commands.  See `ecasound-iam-mode'.
729
730 \(Type \\[describe-mode] in the ecasound buffer for a list of commands.)"
731   (interactive
732    (list
733     (and current-prefix-arg
734          (read-buffer "Ecasound buffer: " "*ecasound*"))))
735   (when (null buffer)
736     (setq buffer "*ecasound*"))
737   (if (not (comint-check-proc buffer))
738       (pop-to-buffer
739        (save-excursion
740          (set-buffer
741           (apply 'make-comint
742                  "ecasound"
743                  ecasound-program
744                  nil
745                  ecasound-arguments))
746          (ecasound-iam-mode)
747          ;; Flush process output
748          (while (accept-process-output
749                  (get-buffer-process (current-buffer))
750                  1))
751          (if (consp ecasound-program)
752              ;; If we're connecting via tcp/ip, we're most probably connecting
753              ;; to a daemon-mode ecasound session.
754              (setq comint-input-sender 'ecasound-network-send
755                    eci-int-output-mode-wellformed-flag t)
756            (let ((eci-hide-output nil))
757              (if (not (eq (eci-command "int-output-mode-wellformed") t))
758                  (message "Failed to initialize properly"))))
759          (when (member "--daemon" ecasound-arguments)
760            (ecasound-setup-daemon))
761          (current-buffer)))
762     (pop-to-buffer buffer)))
763
764 (defun ecasound-setup-daemon ()
765   (let ((cb (current-buffer)))
766     (if (ecasound-daemon-p)
767         (error "Ecasound Daemon %S already initialized" ecasound-daemon)
768       (setq ecasound-daemon
769             (save-excursion
770               (set-buffer
771                (make-comint
772                 "ecasound-daemon"
773                 (cons "localhost" (ecasound-daemon-port))))
774               (ecasound-iam-mode)
775               (setq comint-input-sender 'ecasound-network-send
776                     eci-int-output-mode-wellformed-flag t
777                     ecasound-parent cb)
778               (set (make-variable-buffer-local 'comint-highlight-prompt) nil)
779               (setq comint-output-filter-functions '(ecasound-output-filter))
780               (current-buffer)))
781       (if (ecasound-daemon-p)
782           (progn (add-hook 'kill-buffer 'ecasound-kill-daemon nil t)
783                  (ecasound-setup-timer))
784         (message "Ecasound daemon initialisation failed")))))
785
786 (defun ecasound-delete-last-in-and-output ()
787   "Delete the region of text generated by the last in and output.
788 This is usually used to hide ECI requests from the user."
789   (delete-region
790    (save-excursion (goto-char comint-last-input-end) (forward-line -1)
791                    (unless (looking-at ecasound-prompt-regexp)
792                      (error "Assumed ecasound-prompt"))
793                    (point))
794    comint-last-output-start))
795
796 (make-variable-buffer-local
797  (defvar eci-last-command nil
798    "Last command sent to the ecasound process."))
799
800 (make-variable-buffer-local
801  (defvar ecasound-last-parse-start nil
802    "Where to start parsing if output is received.
803 This marker is advanced everytime a successful parse happens."))
804
805 (defun eci-input-filter (string)
806   "Track commands sent to ecasound.
807 Argument STRING is the input sent."
808   (when (string-match "^[[:space:]]*\\([a-zA-Z-]+\\)[\n\t ]+" string)
809     (setq eci-last-command (match-string-no-properties 1 string)
810           ;; This is a precaution, but it makes sense
811           ecasound-last-parse-start (point))
812     (when (or (string= eci-last-command "quit")
813               (string= eci-last-command "q"))
814       ;; Prevents complete hangup, still a bit mysterius
815       (ecasound-kill-daemon))))
816
817 (defun ecasound-network-send (proc string)
818   "Function for sending to PROC input STRING via network."
819   (comint-send-string proc string)
820   (comint-send-string proc "\r\n"))
821
822 (defcustom ecasound-last-command-alist
823   '(("int-output-mode-wellformed" .
824      (setq eci-int-output-mode-wellformed-flag t))
825     ("int-cmd-list" .
826      (setq ecasound-iam-commands value))
827     ("map-cop-list" .
828      (setq eci-map-cop-list (eci-process-map-list value)))
829     ("map-ladspa-list" .
830      (setq eci-map-ladspa-list (eci-process-map-list value)))
831     ("map-ctrl-list" .
832      (setq eci-map-ctrl-list (eci-process-map-list value)))
833     ("map-preset-list" .
834      (setq eci-map-preset-list (eci-process-map-list value)))
835     ("cop-status" .
836      (eci-process-cop-status value))
837     ("engine-status" .
838      (setq eci-engine-status value))
839     ("cs-selected" .
840      (setq eci-cs-selected value)))
841   "*Alist of command/expression pairs.
842 If `ecasound-last-command' is one of the alist keys, the value of that entry
843 will be evaluated with the variable VALUE bound to the commands
844 result value."
845   :group 'ecasound
846   :type '(alist :key-type (string :tag "Command")
847                 :value-type (sexp :tag "Lisp Expression")))
848                 
849 (defcustom ecasound-type-alist
850   '(("-"  . t)
851     ("i"  . (string-to-number value))
852     ("li" . (string-to-number value))
853     ("f"  . (string-to-number value))
854     ("s"  . value)
855     ("S"  . (split-string value ","))
856     ("e"  . (progn (run-hook-with-args 'ecasound-error-hook value) nil)))
857   "*Alist defining ECI type conversion.
858 Each key is a type, and the values are Lisp expressions.  During evaluation
859 the variables TYPE and VALUE are bound respectively."
860   :group 'ecasound
861   :type '(alist :key-type (string :tag "Type")
862                 :value-type (sexp :tag "Lisp Expression")))
863
864 (defun ecasound-process-result (type value)
865   "Process ecasound result.
866 This function is called if `ecasound-output-filter' detected an ECI reply.
867 Argument TYPE the ECI type as a string and argument VALUE is the value as
868 a string.
869 This function uses `ecasound-type-alist' and `ecasound-last-command-alist'
870 to decide how to transform its arguments."
871   (let ((tcode (member* type ecasound-type-alist :test 'string= :key 'car))
872         (lcode (member* eci-last-command ecasound-last-command-alist
873                         :test 'string= :key 'car)))
874     (if tcode
875         (setq value (eval (cdar tcode)))
876       (error "Return type '%s' not defined in `ecasound-type-alist'" type))
877     (setq eci-return-value value
878           eci-return-type type
879           eci-result
880           (if lcode
881               (eval (cdar lcode))
882             value))))
883
884 (make-variable-buffer-local
885  (defvar eci-return-type nil
886    "The return type of the last received return value as a string."))
887
888 (make-variable-buffer-local
889  (defvar eci-return-value nil
890    "The last received return value as a string."))
891
892 (make-variable-buffer-local
893  (defvar eci-result nil
894    "The last received return value as a Lisp Object."))
895
896 (defun ecasound-output-filter (string)
897   "Parse ecasound process output.
898 This function should be used on `comint-output-filter-functions' hook.
899 STRING is the string originally received and inserted into the buffer."
900   (let ((start (or ecasound-last-parse-start (point-min)))
901         (end (process-mark (get-buffer-process (current-buffer)))))
902     (when (< start end)
903       (save-excursion
904         (let (type value (end (copy-marker end)))
905           (goto-char start)
906           (while (re-search-forward
907                   "\\([0-9]\\{1,3\\}\\) \\([0-9]\\{1,5\\}\\)\\( \\(.*\\)\\)?\n"
908                   end t)
909             (let* ((loglevel (string-to-number (match-string 1)))
910                    (msgsize (string-to-number (match-string 2)))
911                    (return-type (match-string-no-properties 4))
912                    (msg (buffer-substring-no-properties
913                          (point)
914                          (progn
915                            (if (> (- (point-max) (point)) msgsize)
916                              (progn
917                                (forward-char msgsize)
918                                (if (not (save-match-data
919                                           (looking-at
920                                            "\\(\n\n\\|\r\n\r\n\\)")))
921                                    (error "Malformed ECI message")
922                                  (point)))
923                              (point-max))))))
924               (when (= msgsize (length msg))
925                 (if (and (= loglevel 256)
926                          (string= return-type "e"))
927                     (add-text-properties
928                      (match-end 0) (point)
929                      (list 'face 'ecasound-error-face)))
930                 (when ecasound-parse-cleanup-buffer
931                   (delete-region (match-beginning 0) (if (= msgsize 0)
932                                                          (point)
933                                                        (match-end 0)))
934                   (delete-char 1))
935                 (setq ecasound-last-parse-start (point))
936                 (if (not (= loglevel 256))
937                     (run-hook-with-args 'ecasound-message-hook loglevel msg)
938                   (setq value msg
939                         type (if (string-match "\\(.*\\)\r" return-type)
940                                  (match-string 1 return-type)
941                                return-type))))))
942           (when type
943             (ecasound-process-result type value)))))))
944
945 (defmacro defeci (name &optional args doc &rest body)
946   "Defines an ECI command.
947 Argument NAME is used for the function name with eci- as prefix.
948 Optional argument ARGS specifies the arguments this ECI command has.
949 Optional argument DOC is the docstring used for the defined function.
950 BODY can start with keyword arguments to indicated certain special cases.  The
951 following keyword arguments are implemented:
952  :cache VARNAME  The command should try to find a cached version of the result
953                  in VARNAME.
954  :pcomplete VALUE The command can provide programmable completion.  Possible
955                   values are the symbol DOC, which indicates that pcomplete
956                   should echo the docstring of the eci command.  Alternatively
957                   you can provide a sexp which is used for the pcomplete
958                   definition."
959   (let ((sym (intern (format "eci-%S" name)))
960         (pcmpl-sym (intern (format "pcomplete/ecasound-iam-mode/%S" name)))
961         (cmd `(eci-command
962                ,(if args
963                     `(format ,(format "%S %s"
964                                       name (mapconcat #'caddr args ","))
965                              ,@(mapcar
966                                 (lambda (arg)
967                                   `(if (or (stringp ,(car arg))
968                                            (numberp ,(car arg)))
969                                        ,(car arg)
970                                      (mapconcat #'identity ,(car arg) ",")))
971                                 args))
972                   (format "%S" name))
973                buffer-or-process))
974         cache cache-doc pcmpl aliases)
975     (while (keywordp (car body))
976       (case (pop body)
977         (:cache (setq cache (pop body)))
978         (:cache-doc (setq cache-doc (pop body)))
979         (:pcomplete (setq pcmpl (pop body)))
980         (:alias (setq aliases (pop body)))
981         (t (pop body))))
982     (when (and (not (eq aliases nil))
983                (not (consp aliases)))
984       (setq aliases (list aliases)))
985     `(progn
986      ,(if cache
987           `(make-variable-buffer-local
988             (defvar ,cache ,@(if cache-doc (list nil cache-doc) (list nil)))))
989      (defun ,sym
990        ,(if args (append (mapcar #'car args) `(&optional buffer-or-process))
991           `(&optional buffer-or-process))
992        ,(if doc doc "")
993        ,(if args `(interactive
994                    ,(if (let (done)
995                           (mapcar (lambda (x) (when x (setq done t)))
996                                   (mapcar #'stringp (mapcar #'cadr args)))
997                           done)
998                         (mapconcat #'identity (mapcar #'cadr args) "\n")
999                       `(list ,@(mapcar #'cadr args))))
1000           `(interactive))
1001        ,@(cond
1002           ((and cache (eq body nil))
1003            `((let ((cached (with-current-buffer
1004                                (ecasound-find-buffer buffer-or-process)
1005                              ,(or cache (and (ecasound-daemon-p)
1006                                              (with-current-buffer
1007                                                  ecasound-daemon
1008                                                ,cache))))))
1009                (if cached
1010                    cached
1011                  ,cmd))))
1012           ((eq body nil)
1013            `(,cmd))
1014           (t body)))
1015      ,@(mapcar
1016         (lambda (alias) `(defalias ',(intern (format "eci-%S" alias))
1017                            ',sym)) aliases)
1018      ,(when pcmpl
1019         `(progn
1020            ,(if (and (eq pcmpl 'doc) (stringp doc) (not (string= doc "")))
1021                 `(defun ,pcmpl-sym ()
1022                    (message ,doc)
1023                    (throw 'pcompleted t))
1024               `(defun ,pcmpl-sym ()
1025                  ,pcmpl))
1026            ,@(mapcar
1027               (lambda (alias)
1028                 `(defalias ',(intern (format "pcomplete/ecasound-iam-mode/%S" alias))
1029                    ',pcmpl-sym))
1030               aliases))))))
1031
1032 (defeci map-cop-list ()
1033   "Returns a list of registered chain operators."
1034   :cache eci-map-cop-list
1035   :cache-doc "If non-nil, contains the chainop object map.
1036 It has the form
1037  ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1038
1039 Use `eci-map-cop-list' to fill this variable with data.")
1040
1041 (defeci map-ctrl-list ()
1042   "Returns a list of registered controllers."
1043   :cache eci-map-ctrl-list
1044   :cache-doc "If non-nil, contains the chainop controller object map.
1045 It has the form
1046  ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1047
1048 Use `eci-map-ctrl-list' to fill this list with data.")
1049
1050 (defeci map-ladspa-list ()
1051   "Returns a list of registered LADSPA plugins."
1052   :cache eci-map-ladspa-list
1053   :cache-doc "If non-nil, contains the LADSPA object map.
1054 It has the form
1055  ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1056
1057 Use `eci-map-ladspa-list' to fill this list with data.")
1058
1059 (defeci map-preset-list ()
1060   "Returns a list of registered effect presets."
1061   :cache eci-map-preset-list
1062   :cache-doc "If non-nil, contains the preset object map.
1063 It has the form
1064  ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1065
1066 Use `eci-map-preset-list' to fill this list with data.")
1067
1068 ;;; Ecasound-iam-mode pcomplete functions
1069
1070 (defun ecasound-iam-setup-pcomplete ()
1071   "Setup buffer-local functions for pcomplete in `ecasound-iam-mode'."
1072   (set (make-local-variable 'pcomplete-command-completion-function)
1073        (lambda ()
1074          (pcomplete-here (if ecasound-iam-commands
1075                              ecasound-iam-commands
1076                            (eci-hide-output eci-int-cmd-list)))))
1077   (set (make-local-variable 'pcomplete-command-name-function)
1078        (lambda ()
1079          (pcomplete-arg 'first)))
1080   (set (make-local-variable 'pcomplete-parse-arguments-function)
1081        'ecasound-iam-pcomplete-parse-arguments))
1082
1083 (defun ecasound-iam-pcomplete-parse-arguments ()
1084   "Parse arguments in the current region.
1085 \" :,\" are considered for splitting."
1086   (let ((begin (save-excursion (comint-bol nil) (point)))
1087         (end (point))
1088         begins args)
1089     (save-excursion
1090       (goto-char begin)
1091       (while (< (point) end)
1092         (skip-chars-forward " \t\n,:")
1093         (setq begins (cons (point) begins))
1094         (let ((skip t))
1095           (while skip
1096             (skip-chars-forward "^ \t\n,:")
1097             (if (eq (char-before) ?\\)
1098                 (skip-chars-forward " \t\n,:")
1099               (setq skip nil))))
1100         (setq args (cons (buffer-substring-no-properties
1101                           (car begins) (point))
1102                          args)))
1103       (cons (reverse args) (reverse begins)))))
1104
1105 (defun ecasound-input-file-or-device ()
1106   "Return a list of possible completions for input device name."
1107   (append (delq
1108            nil
1109            (mapcar
1110             (lambda (elt)
1111               (when (string-match
1112                      (concat "^" (regexp-quote pcomplete-stub)) elt)
1113                 elt))
1114             (list "alsa" "alsahw" "alsalb" "alsaplugin"
1115                   "arts" "loop" "null" "stdin")))
1116           (pcomplete-entries)))
1117
1118 ;;;; IAM commands
1119
1120 (defun eci-map-find-args (arg map)
1121   "Return the argument specification for ARG in MAP."
1122   (let (result)
1123     (while map
1124       (if (string= (nth 1 (car map)) arg)
1125           (setq result (nthcdr 3 (car map))
1126                 map nil)
1127         (setq map (cdr map))))
1128     result))
1129
1130 (defun ecasound-echo-arg (arg)
1131   "Display a chain operator parameter description from a eci-map-*-list
1132 variable."
1133   (if arg
1134       (let ((type (nth 5 arg)))
1135         (message "%s%s%s, default %S%s%s"
1136                  (car arg)
1137                  (if type (format " (%S)" type) "")
1138                  (if (and (not (string= (nth 1 arg) ""))
1139                           (not (string= (car arg) (nth 1 arg))))
1140                      (format " (%s)" (nth 1 arg))
1141                    "")
1142                  (nth 2 arg)
1143                  (if (nth 4 arg) (format " min %S" (nth 4 arg)) "")
1144                  (if (nth 3 arg) (format " max %S" (nth 3 arg)) "")))
1145     (message "No help available")))
1146
1147 \f
1148 ;;; ECI --- The Ecasound Control Interface
1149
1150 (defgroup eci nil
1151   "Ecasound Control Interface."
1152   :group 'ecasound)
1153
1154 (defcustom eci-program (or (getenv "ECASOUND") "ecasound")
1155   "*Program to invoke when doing `eci-init'."
1156   :group 'eci
1157   :type '(choice string (cons string string)))
1158
1159 (defcustom eci-arguments '("-c" "-D" "-d:256")
1160   "*Arguments used by `eci-init'."
1161   :group 'eci
1162   :type 'ecasound-args)
1163
1164 (defvar eci-hide-output nil
1165   "If non-nil, `eci-command' will remove the output generated.")
1166
1167 (defmacro eci-hide-output (&rest eci-call)
1168   "Hide the output of this ECI-call.
1169 If a daemon-channel is active, use that, otherwise set `eci-hide-output' to t.
1170 Argument ECI-CALL is a symbol followed by its aruments if any."
1171   `(if (ecasound-daemon-p)
1172        ,(append eci-call (list 'ecasound-daemon))
1173      (let ((eci-hide-output t))
1174        ,eci-call)))
1175
1176 (defun eci-init ()
1177   "Initialize a programmatic ECI session.
1178 Every call to this function results in a new sub-process being created
1179 according to `eci-program' and `eci-arguments'.  Returns the newly
1180 created buffer.
1181 The caller is responsible for terminating the subprocess at some point."
1182   (save-excursion
1183     (set-buffer
1184      (apply 'make-comint
1185             "eci-ecasound"
1186             eci-program
1187             nil
1188             eci-arguments))
1189     (ecasound-iam-mode)
1190     (while (accept-process-output (get-buffer-process (current-buffer)) 1))
1191     (if (eci-command "int-output-mode-wellformed")
1192         (current-buffer))))
1193
1194 (defun eci-interactive-startup ()
1195   "Used to interactively startup a ECI session using `eci-init'.
1196 This will mostly be used for testing sessions and is equivalent
1197 to `ecasound'."
1198   (interactive)
1199   (switch-to-buffer (eci-init)))
1200
1201 (defun ecasound-find-buffer (buffer-or-process)
1202   (cond
1203    ((bufferp buffer-or-process)
1204     buffer-or-process)
1205    ((processp buffer-or-process)
1206     (process-buffer buffer-or-process))
1207    ((and (eq major-mode 'ecasound-iam-mode)
1208          (comint-check-proc (current-buffer)))
1209     (current-buffer))
1210    (t (error "Could not determine suitable ecasound buffer"))))
1211
1212 (defun ecasound-find-parent (buffer-or-process)
1213   (with-current-buffer (ecasound-find-buffer buffer-or-process)
1214     (if ecasound-parent
1215         ecasound-parent
1216       (current-buffer))))
1217
1218 (defun eci-command (command &optional buffer-or-process)
1219   "Send a ECI command to a ECI host process.
1220 COMMAND is the string to be sent, without a newline character.
1221 If BUFFER-OR-PROCESS is nil, first look for a ecasound process in the current
1222 buffer, then for a ecasound buffer with the name *ecasound*,
1223 otherwise use the buffer or process supplied.
1224 Return the string we received in reply to the command except
1225 `eci-int-output-mode-wellformed-flag' is set, which means we can parse the
1226 output via `eci-parse' and return a meaningful value."
1227   (interactive "sECI Command: ")
1228   (let* ((buf (ecasound-find-buffer buffer-or-process))
1229          (proc (get-buffer-process buf))
1230          (ecasound-sending-command t))
1231     (with-current-buffer buf
1232       (let ((moving (= (point) (point-max))))
1233         (setq eci-result 'waiting)
1234         (goto-char (process-mark proc))
1235         (insert command)
1236         (let (comint-eol-on-send)
1237           (comint-send-input))
1238         (let ((here (point)) result)
1239           (while (eq eci-result 'waiting)
1240             (accept-process-output proc 0 30))
1241           (setq result
1242                 (if eci-int-output-mode-wellformed-flag
1243                     eci-result
1244                   ;; Backward compatibility.  Just return the string
1245                   (buffer-substring-no-properties here (save-excursion
1246                                         ; Strange hack to avoid fields
1247                                                          (forward-char -1)
1248                                                          (beginning-of-line)
1249                                                          (if (not (= here (point)))
1250                                                              (forward-char -1))
1251                                                          (point)))))
1252           (if moving (goto-char (point-max)))
1253           (when (and eci-hide-output result)
1254             (ecasound-delete-last-in-and-output))
1255           result)))))
1256
1257 (defsubst eci-error-p ()
1258   "Predicate which can be used to check if the last command produced an error."
1259   (string= eci-return-type "e"))
1260
1261 ;;; ECI commands implemented as lisp functions
1262
1263 (defeci int-cmd-list ()
1264   ""
1265   :cache ecasound-iam-commands
1266   :cache-doc "Available Ecasound IAM commands.")
1267
1268 (defeci run)
1269
1270 (defeci start)
1271
1272 (defeci cs-add ((chainsetup "sChainsetup to add: " "%s"))
1273   "Adds a new chainsetup with name `name`."
1274   :pcomplete doc)
1275
1276 (defeci cs-connect ()
1277   "Connect currently selected chainsetup to engine."
1278   :pcomplete doc)
1279
1280 (defeci cs-connected ()
1281   "Returns the name of currently connected chainsetup."
1282   :pcomplete doc)
1283
1284 (defeci cs-disconnect ()
1285   "Disconnect currently connected chainsetup."
1286   :pcomplete doc)
1287
1288 (defeci cs-forward
1289   ((seconds
1290     (if current-prefix-arg
1291         (prefix-numeric-value current-prefix-arg)
1292       (read-minibuffer (format "Time in seconds to forward %s: "
1293                                (eci-hide-output eci-cs-selected)))) "%f")))
1294
1295 (defeci cs-get-length ()
1296   ""
1297   :alias get-length)
1298
1299 (defeci cs-get-length-samples ()
1300   ""
1301   :alias get-length-samples)
1302
1303 (defeci cs-get-position ()
1304   ""
1305   :alias (cs-getpos getpos get-position))
1306
1307 (defeci cs-index-select ((index "nChainsetup index: " "%d"))
1308   ""
1309   :alias cs-iselect)
1310
1311 (defeci cs-is-valid ()
1312   "Whether currently selected chainsetup is valid (=can be connected)?"
1313   :pcomplete doc
1314   (let ((val (eci-command "cs-is-valid" buffer-or-process)))
1315     (if (interactive-p)
1316         (message (format "Chainsetup is%s valid" (if (= val 0) "" " not"))))
1317     val))
1318
1319 (defun eci-cs-is-valid-p (&optional buffer-or-process)
1320   "Predicate function used to determine chain setup validity."
1321   (case (eci-cs-is-valid buffer-or-process)
1322     (1 t)
1323     (0 nil)
1324     (otherwise (error "Unexcpected return value from cs-is-valid"))))
1325
1326 (defeci cs-list ()
1327   "Returns a list of all chainsetups."
1328   :pcomplete doc
1329   (let ((val (eci-command "cs-list" buffer-or-process)))
1330     (if (interactive-p)
1331         (message (concat "Available chainsetups: "
1332                          (mapconcat #'identity val ", "))))
1333     val))
1334
1335 (defeci cs-load ((filename "fChainsetup filename: " "%s"))
1336   "Adds a new chainsetup by loading it from file FILENAME.
1337 FILENAME is then the selected chainsetup."
1338   :pcomplete (pcomplete-here (pcomplete-entries)))
1339
1340 (defeci cs-remove ()
1341   "Removes currently selected chainsetup."
1342   :pcomplete doc)
1343
1344 (defeci cs-rewind
1345   ((seconds
1346     (if current-prefix-arg
1347         (prefix-numeric-value current-prefix-arg)
1348       (read-minibuffer "Time in seconds to rewind chainsetup: ")) "%f"))
1349   "Rewinds the current chainsetup position by `time-in-seconds` seconds."
1350   :pcomplete doc
1351   :alias (rewind rw))
1352
1353 (defeci cs-save)
1354
1355 (defeci cs-save-as ((filename "FChainsetup filename: " "%s"))
1356   "Saves currently selected chainsetup to file FILENAME."
1357   :pcomplete (pcomplete-here (pcomplete-entries)))
1358
1359 (defeci cs-selected ()
1360   "Returns the name of currently selected chainsetup."
1361   :pcomplete doc
1362   (let ((val (with-current-buffer (ecasound-find-parent buffer-or-process)
1363                (setq eci-cs-selected (eci-command "cs-selected"
1364                                                   buffer-or-process)))))
1365     (if (interactive-p)
1366         (message (format "Selected chainsetup: %s" val)))
1367     val))
1368
1369 (defeci cs-status)
1370
1371 (defeci c-add ((chains "sChain(s) to add: " "%s"))
1372   "Adds a set of chains.  Added chains are automatically selected.
1373 If argument CHAINS is a list, its elements are concatenated with ','.")
1374
1375 (defeci c-clear ()
1376   "Clear selected chains by removing all chain operators and controllers.
1377 Doesn't change how chains are connected to inputs and outputs."
1378   :pcomplete doc)
1379
1380 (defun ecasound-read-list (prompt list)
1381   "Interactively prompt for a number of inputs until empty string.
1382 PROMPT is used as prompt and LIST is a list of choices to choose from."
1383   (let ((avail list)
1384         result current)
1385     (while
1386         (and avail
1387              (not
1388               (string=
1389                (setq current (completing-read prompt (mapcar #'list avail)))
1390                "")))
1391       (setq result (cons current result)
1392             avail (delete current avail)))
1393     (nreverse result)))
1394
1395 (defeci c-deselect
1396   ((chains (ecasound-read-list "Chain to deselect: " (eci-c-selected)) "%s"))
1397   "Deselects chains."
1398   :pcomplete (while (pcomplete-here (eci-c-selected))))
1399
1400 (defeci c-list ()
1401   "Returns a list of all chains.")
1402
1403 (defeci c-mute ()
1404   "Toggle chain muting.  When chain is muted, all data that goes
1405 through is muted."
1406   :pcomplete doc)
1407
1408 (defeci c-select ((chains (ecasound-read-list "Chain: " (eci-c-list)) "%s"))
1409   "Selects chains.  Other chains are automatically deselected."
1410   :pcomplete doc)
1411
1412 (defeci c-selected ()
1413   ""
1414   (let ((val (eci-command "c-selected" buffer-or-process)))
1415     (if (interactive-p)
1416         (if (null val)
1417             (message "No selected chains")
1418           (message (concat "Selected chains: "
1419                            (mapconcat #'identity val ", ")))))
1420     val))
1421
1422 (defeci c-select-all ()
1423   "Selects all chains."
1424   :pcomplete doc)
1425
1426 (defeci cs-select
1427   ((chainsetup
1428     (completing-read "Chainsetup: " (mapcar #'list (eci-cs-list)))
1429     "%s"))
1430   ""
1431   :pcomplete (pcomplete-here (eci-hide-output eci-cs-list)))
1432
1433 (defeci ai-add
1434   ((ifstring
1435     (let ((file (read-file-name "Input filename: ")))
1436       (if (file-exists-p file)
1437           (expand-file-name file)
1438         file))
1439     "%s"))
1440   "Adds a new input object."
1441   :pcomplete (pcomplete-here (ecasound-input-file-or-device)))
1442
1443 (defeci ai-attach ()
1444   "Attaches the currently selected audio input object to all selected chains."
1445   :pcomplete doc)
1446
1447 (defeci ai-forward
1448   ((seconds
1449     (if current-prefix-arg
1450         (prefix-numeric-value current-prefix-arg)
1451       (read-minibuffer (format "Time in seconds to forward %s: "
1452                                (eci-hide-output eci-ai-selected)))) "%f"))
1453   "Selected audio input object is forwarded by SECONDS.
1454 Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1455   :pcomplete doc
1456   :alias ai-fw)
1457
1458 (defeci ai-rewind
1459   ((seconds
1460     (if current-prefix-arg
1461         (prefix-numeric-value current-prefix-arg)
1462       (read-minibuffer (format "Time in seconds to rewind %s: "
1463                                (eci-hide-output eci-ai-selected)))) "%f"))
1464   "Selected audio input object is rewinded by SECONDS.
1465 Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1466   :pcomplete doc
1467   :alias ai-rw)
1468
1469 (defeci ai-index-select ((index "nAudio Input index: " "%d"))
1470   "Select some audio input object based on a short index.
1471 Especially file names can be rather long.  This command can be used to avoid
1472 typing these long names when selecting audio objects.
1473 INDEX is an integer value, where 1 refers to the first audio input.
1474 You can use `eci-ai-list' to get a full list of currently available inputs."
1475   :pcomplete doc
1476   :alias ai-iselect)
1477
1478 (defeci ai-list)
1479
1480 (defeci ai-remove ()
1481   "Removes the currently selected audio input object from the chainsetup."
1482   :pcomplete doc)
1483 (defeci ao-remove ()
1484   "Removes the currently selected audio output object from the chainsetup."
1485   :pcomplete doc)
1486
1487 (defeci ai-select ((name "sAudio Input Object name: " "%s"))
1488   "Selects an audio object.
1489 NAME refers to the string used when creating the object.  Note! All input
1490 object names are required to be unique.  Similarly all output names need to be
1491 unique.  However, it's possible that the same object name exists both as an
1492 input and as an output."
1493   :pcomplete (pcomplete-here (eci-hide-output eci-ai-list)))
1494
1495 (defeci ai-selected ()
1496   "Returns the name of the currently selected audio input object."
1497   :pcomplete doc)
1498
1499 (defeci ao-add ((filename "FOutput filename: " "%s"))
1500   ""
1501   :pcomplete (pcomplete-here (ecasound-input-file-or-device)))
1502
1503 (defeci ao-add-default)
1504
1505 (defeci ao-attach ()
1506   "Attaches the currently selected audio output object to all selected chains."
1507   :pcomplete doc)
1508
1509 (defeci ao-forward
1510   ((seconds
1511     (if current-prefix-arg
1512         (prefix-numeric-value current-prefix-arg)
1513       (read-minibuffer (format "Time in seconds to forward %s: "
1514                                (eci-hide-output eci-ao-selected)))) "%f"))
1515   "Selected audio output object is forwarded by SECONDS.
1516 Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1517   :pcomplete doc
1518   :alias ao-fw)
1519
1520 (defeci ao-index-select ((index "nAudio Output index: " "%d"))
1521   "Select some audio output object based on a short index.
1522 Especially file names can be rather long.  This command can be used to avoid
1523 typing these long names when selecting audio objects.
1524 INDEX is an integer value, where 1 refers to the first audio output.
1525 You can use `eci-ao-list' to get a full list of currently available outputs."
1526   :pcomplete doc
1527   :alias ao-iselect)
1528
1529 (defeci ao-list)
1530
1531 (defeci ao-rewind
1532   ((seconds
1533     (if current-prefix-arg
1534         (prefix-numeric-value current-prefix-arg)
1535       (read-minibuffer (format "Time in seconds to rewind %s: "
1536                                (eci-hide-output eci-ai-selected)))) "%f"))
1537   "Selected audio output object is rewinded by SECONDS.
1538 Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1539   :pcomplete doc
1540   :alias ai-rw)
1541
1542 (defeci ao-select ((name "sAudio Output Object name: " "%s"))
1543   "Selects an audio object.
1544 NAME refers to the string used when creating the object.  Note! All output
1545 object names need to be unique.  However, it's possible that the same object
1546 name exists both as an input and as an output."
1547   :pcomplete (pcomplete-here (eci-hide-output eci-ao-list)))
1548
1549 (defeci ao-selected ()
1550   "Returns the name of the currently selected audio output object."
1551   :pcomplete doc)
1552
1553 (defeci engine-status ()
1554   "Returns a string describing the engine status
1555 \(running, stopped, finished, error, not ready)."
1556   :pcomplete doc
1557   (with-current-buffer (ecasound-find-parent buffer-or-process)
1558     (setq eci-engine-status (eci-command "engine-status" buffer-or-process))))
1559
1560 (defmacro ecasound-complete-cop-map (map)
1561   (let ((m (intern (format "eci-map-%S-list" map))))
1562     `(progn
1563        (cond
1564         ((= pcomplete-last 2)
1565          (pcomplete-next-arg)
1566          (pcomplete-here
1567           (sort (mapcar (lambda (elt) (nth 1 elt))
1568                         (eci-hide-output ,m))
1569                 #'string-lessp)))
1570         ((> pcomplete-last 2)
1571          (ecasound-echo-arg
1572           (nth (- pcomplete-last 3)
1573                (eci-map-find-args
1574                 (pcomplete-arg -1) (eci-hide-output ,m)))))))))
1575
1576 (defeci cop-add
1577   ((string
1578     (if current-prefix-arg
1579         (read-string "Chainop to add: " "-")
1580       (let* ((cop
1581               (completing-read
1582                "Chain operator: "
1583                (append (eci-hide-output eci-map-cop-list)
1584                        (eci-hide-output eci-map-ladspa-list)
1585                        (eci-hide-output eci-map-preset-list))))
1586              (entry (or (assoc cop (eci-map-cop-list))
1587                         (assoc cop (eci-map-ladspa-list))
1588                         (assoc cop (eci-map-preset-list))))
1589              (arg (nth 1 entry)))
1590         (concat
1591          (cond
1592           ((assoc cop (eci-map-cop-list))
1593            (concat "-" arg ":"))
1594           ((assoc cop (eci-map-ladspa-list))
1595            (concat "-el:" arg ","))
1596           ((assoc cop (eci-map-preset-list))
1597            (concat "-pn:" arg ",")))
1598          (mapconcat #'ecasound-read-copp (nthcdr 3 entry) ","))))
1599     "%s"))
1600   ""
1601   :pcomplete
1602   (progn
1603     (cond
1604      ((= pcomplete-last 1)
1605       (pcomplete-here
1606        (append
1607         '("-el:" "-pn:")
1608         (mapcar
1609          (lambda (elt)
1610            (concat "-" (nth 1 elt) ":"))
1611          (eci-hide-output eci-map-cop-list)))))
1612      ((string= (pcomplete-arg) "-el")
1613       (ecasound-complete-cop-map ladspa))
1614      ((string= (pcomplete-arg) "-pn")
1615       (ecasound-complete-cop-map preset))
1616      ((> pcomplete-last 1)
1617       (ecasound-echo-arg
1618        (nth (- pcomplete-last 2)
1619             (eci-map-find-args
1620              (substring (pcomplete-arg) 1)
1621              (eci-hide-output eci-map-cop-list))))))
1622     (throw 'pcompleted t)))
1623
1624 (defeci cop-list)
1625
1626 (defeci cop-remove)
1627
1628 (defeci cop-select
1629   ((index "nChainop to select: " "%d")))
1630
1631 (defeci cop-selected)
1632
1633 ;; FIXME: Command seems to be broken in CVS.
1634 (defeci cop-set ((cop "nChainop id: " "%d")
1635                  (copp "nParameter id: " "%d")
1636                  (value "nValue: " "%f"))
1637   "Changes the value of a single chain operator parameter.
1638 Unlike other chain operator commands, this can also be used during processing."
1639   :pcomplete doc)
1640
1641 (defeci ctrl-add
1642   ((string
1643     (if current-prefix-arg
1644         (read-string "Controller to add: " "-")
1645       (let ((ctrl (assoc
1646                    (completing-read
1647                     "Chain operator controller controller: "
1648                     (eci-hide-output eci-map-ctrl-list))
1649                    (eci-hide-output eci-map-ctrl-list))))
1650         (concat "-" (nth 1 ctrl) ":"
1651                 (mapconcat #'ecasound-read-copp (nthcdr 3 ctrl) ","))))
1652     "%s")))
1653
1654 (defeci ctrl-select
1655   ((index "nController to select: " "%d")))
1656
1657 (defeci copp-select
1658   ((index "nChainop parameter to select: " "%d")))
1659
1660 (defeci copp-get)
1661
1662 (defeci copp-set
1663   ((value "nValue for Chain operator parameter: " "%f")))
1664
1665 ;;;; ECI Examples
1666
1667 (defun eci-example ()
1668   "Implements the example given in the ECI documentation."
1669   (interactive)
1670   (save-current-buffer
1671     (set-buffer (eci-init))
1672     (display-buffer (current-buffer))
1673     (eci-cs-add "play_chainsetup")
1674     (eci-c-add "1st_chain")
1675     (call-interactively #'eci-ai-add)
1676     (eci-ao-add "/dev/dsp")
1677     (eci-cop-add "-efl:100")
1678     (eci-cop-select 1) (eci-copp-select 1)
1679     (eci-cs-connect)
1680     (eci-command "start")
1681     (sit-for 1)
1682     (while (and (string= (eci-engine-status) "running")
1683                 (< (eci-get-position) 15))
1684       (eci-copp-set (+ (eci-copp-get) 500))
1685       (sit-for 1))
1686     (eci-command "stop")
1687     (eci-cs-disconnect)
1688     (message (concat "Chain operator status: "
1689                       (eci-command "cop-status")))))
1690
1691 (defun eci-make-temp-file-name (suffix)
1692   (concat (make-temp-name
1693            (expand-file-name "emacs-eci" temporary-file-directory))
1694           suffix))
1695
1696 (defun ecasound-read-from-minibuffer (prompt default)
1697   (let ((result (read-from-minibuffer
1698                  (format "%s (default %S): " prompt default)
1699                  nil nil nil nil default)))
1700     (if (and result (not (string= result "")))
1701         result
1702       default)))
1703
1704 (defconst ecasound-signalview-clipped-threshold (- 1.0 (/ 1.0 16384)))
1705
1706 (defconst ecasound-signalview-bar-length 55)
1707
1708 (defun ecasound-position-to-string (secs &optional long)
1709   "Convert a floating point position value in SECS to a string.
1710 If optional argument LONG is non-nil, produce a full 00:00.00 string,
1711 otherwise ignore zeors as well as colons and dots on the left side."
1712   (let ((str (format "%02d:%02d.%02d"
1713                      (/ secs 60)
1714                      (% (round (floor secs)) 60)
1715                      (* (- secs (floor secs)) 100))))
1716     (if long
1717         str
1718       (let ((idx 0) (len (1- (length str))))
1719         (while (and (< idx len)
1720                     (let ((ch (aref str idx)))
1721                       (or (eq ch ?0) (eq ch ?:) (eq ch ?.))))
1722           (incf idx))
1723         (substring str idx)))))
1724
1725 (defun ecasound-signalview (bufsize format input output)
1726   "Interactively view the singal of a audio stream.
1727 After invokation, this function displays the signal level of the individual
1728 channels in INPUT based on the information given in FORMAT."
1729   (interactive
1730    (list
1731     (ecasound-read-from-minibuffer "Buffersize" "128")
1732     (ecasound-read-from-minibuffer "Format" "s16_le,2,44100,i")
1733     (let ((file (read-file-name "Input: ")))
1734       (if (file-exists-p file)
1735           (expand-file-name file)
1736         file))
1737     (ecasound-read-from-minibuffer "Output" "null")))
1738   (let* (;; THis saves time
1739          (ecasound-parse-cleanup-buffer nil)
1740          (handle (eci-init))
1741          (channels (string-to-number (nth 1 (split-string format ","))))
1742          (chinfo (make-vector channels nil)))
1743     (dotimes (ch channels) (aset chinfo ch (cons 0 0)))
1744     (eci-cs-add "signalview" handle)
1745     (eci-c-add "analysis" handle)
1746     (eci-cs-set-audio-format format handle)
1747     (eci-ai-add input handle)
1748     (eci-ao-add output handle)
1749     (eci-cop-add "-evp" handle)
1750     (eci-cop-add "-ev" handle)
1751     (set-buffer (get-buffer-create "*Ecasound-signalview*"))
1752     (erase-buffer)
1753     (dotimes (ch channels)
1754       (insert "---\n"))
1755     (setq header-line-format
1756          (list (concat "Channel#"
1757                        (make-string (- ecasound-signalview-bar-length 3) 32)
1758                        "| max-value  clipped")))
1759     (set (make-variable-buffer-local 'ecasignalview-position) "unknown")
1760     (set (make-variable-buffer-local 'ecasignalview-engine-status) "unknown")
1761     (setq mode-line-format
1762           (list
1763            (list
1764             (- ecasound-signalview-bar-length 3)
1765             (format "Input: %s, output: %s" input output)
1766             'ecasignalview-engine-status)
1767            " | " 'ecasignalview-position))
1768     (switch-to-buffer-other-window (current-buffer))
1769     (eci-cs-connect handle)
1770     (eci-start handle)
1771     (sit-for 0.8)
1772     (eci-cop-select 1 handle)
1773     (while (string= (setq ecasignalview-engine-status
1774                           (eci-engine-status handle)) "running")
1775       (let ((inhibit-quit t) (inhibit-redisplay t))
1776         (setq ecasignalview-position
1777               (ecasound-position-to-string (eci-cs-get-position handle) t))
1778         (delete-region (point-min) (point-max))
1779         (dotimes (ch channels)
1780           (insert (format "ch%d: " (1+ ch)))
1781           (let ((val (progn (eci-copp-select (1+ ch) handle)
1782                             (eci-copp-get handle)))
1783                 (bl ecasound-signalview-bar-length))
1784             (insert
1785              (concat
1786               (make-string (round (* val bl)) ?*)
1787               (make-string (- bl (round (* val bl))) ? )))
1788             (if (> val (car (aref chinfo ch)))
1789                 (setcar (aref chinfo ch) val))
1790             (if (> val ecasound-signalview-clipped-threshold)
1791               (incf (cdr (aref chinfo ch))))
1792             (insert (format "| %.4f     %d\n" (car (aref chinfo ch))
1793                             (cdr (aref chinfo ch))))))
1794         (goto-char (point-min)))
1795       (sit-for 0.1)
1796       (fit-window-to-buffer))
1797     (goto-char (point-max))
1798     (let ((pos (point)))
1799       (insert
1800        (nth 2
1801             (nth 2
1802                  (nthcdr 2
1803                          (assoc "Volume analysis"
1804                                 (assoc "analysis"
1805                                        (eci-cop-status handle)))))))
1806       (goto-char pos))
1807     (recenter channels)
1808     (fit-window-to-buffer)))
1809
1810 (defun ecasound-normalize (filename)
1811   "Normalize a audio file using ECI."
1812   (interactive "fFile to normalize: ")
1813   (let ((tmpfile (eci-make-temp-file-name ".wav")))
1814     (unwind-protect
1815         (with-current-buffer (eci-init)
1816           (display-buffer (current-buffer)) (sit-for 1)
1817           (eci-cs-add "analyze") (eci-c-add "1")
1818           (eci-ai-add filename) (eci-ao-add tmpfile)
1819           (eci-cop-add "-ev")
1820           (message "Analyzing sample data...")
1821           (eci-cs-connect) (eci-run)
1822           (eci-cop-select 1) (eci-copp-select 2)
1823           (let ((gainfactor (eci-copp-get)))
1824             (eci-cs-disconnect)
1825             (if (<= gainfactor 1)
1826                 (message "File already normalized!")
1827               (eci-cs-add "apply") (eci-c-add "1")
1828               (eci-ai-add tmpfile) (eci-ao-add filename)
1829               (eci-cop-add "-ea:100")
1830               (eci-cop-select 1)
1831               (eci-copp-select 1)
1832               (eci-copp-set (* gainfactor 100))
1833               (eci-cs-connect) (eci-run) (eci-cs-disconnect)
1834               (message "Done"))))
1835       (if (file-exists-p tmpfile)
1836           (delete-file tmpfile)))))
1837
1838 ;;; Utility functions for converting strings to data-structures.
1839
1840 (defvar eci-cop-status-header
1841   "### Chain operator status (chainsetup '\\([^']+\\)') ###\n")
1842
1843 (defun eci-process-cop-status (string)
1844   (with-temp-buffer
1845     (insert string) (goto-char (point-min))
1846     (when (re-search-forward eci-cop-status-header nil t)
1847       (let (result)
1848         (while (re-search-forward "Chain \"\\([^\"]+\\)\":\n" nil t)
1849           (let ((c (match-string-no-properties 1)) chain)
1850             (while (re-search-forward
1851                     "\t\\([0-9]+\\)\\. \\(.+\\): \\(.*\\)\n?" nil t)
1852               (let ((n (string-to-number (match-string 1)))
1853                     (name (match-string-no-properties 2))
1854                     (args
1855                      (mapcar
1856                       (lambda (elt)
1857                         (when (string-match
1858                                "\\[\\([0-9]+\\)\\] \\(.*\\) \\([0-9.-]+\\)$"
1859                                elt)
1860                           (list (match-string-no-properties 2 elt)
1861                                 (string-to-number (match-string 1 elt))
1862                                 (string-to-number (match-string 3 elt)))))
1863                       (split-string
1864                        (match-string-no-properties 3) ", "))))
1865                 (if (looking-at "\tStatus info:\n")
1866                     (setq args
1867                           (append
1868                            args
1869                            (list
1870                             (list
1871                              "Status info" nil
1872                              (buffer-substring
1873                               (progn (forward-line 1) (point))
1874                               (or (re-search-forward "\n\n" nil t)
1875                                   (point-max))))))))
1876                 (setq chain (cons (append (list name n) args) chain))))
1877             (setq result (cons (reverse (append chain (list c))) result))))
1878         result))))
1879
1880 (defun eci-process-map-list (string)
1881   "Parse the output of a map-xxx-list ECI command and return an alist.
1882 STRING is the string returned by a map-xxx-list command."
1883   (mapcar
1884    (lambda (elt)
1885      (append
1886       (list (nth 1 elt) (nth 0 elt) (nth 2 elt))
1887       (let (res (count (string-to-number (nth 3 elt))))
1888         (setq elt (nthcdr 4 elt))
1889         (while (> count 0)
1890           (setq
1891            res
1892            (cons
1893             (list (nth 0 elt) (nth 1 elt)
1894                   (string-to-number (nth 2 elt)) ;; default value
1895                   (when (string= (nth 3 elt) "1")
1896                     (string-to-number (nth 4 elt)))
1897                   (when (string= (nth 5 elt) "1")
1898                     (string-to-number (nth 6 elt)))
1899                   (cond
1900                    ((string= (nth 7 elt) "1")
1901                     'toggle)
1902                    ((string= (nth 8 elt) "1")
1903                     'integer)
1904                    ((string= (nth 9 elt) "1")
1905                     'logarithmic)
1906                    ((string= (nth 10 elt) "1")
1907                     'output))) res)
1908            elt (nthcdr 11 elt)
1909            count (1- count)))
1910         (reverse res))))
1911    (mapcar (lambda (str) (split-string str ","))
1912            (split-string string "\n"))))
1913
1914 (defeci cs-set-audio-format
1915   ((format (ecasound-read-from-minibuffer
1916             "Audio format" "s16_le,2,44100,i") "%s"))
1917   "Set the default sample parameters for currently selected chainsetup.
1918 For example cd-quality audio would be \"16,2,44100\"."
1919   :pcomplete doc)
1920
1921 (defeci cop-register)
1922 (defeci preset-register)
1923 (defeci ctrl-register)
1924
1925 (defeci cop-status)
1926
1927 (defeci ladspa-register)
1928
1929 (defun ecasound-read-copp (copp)
1930   "Interactively read one chainop parameter."
1931   (let* ((completion-ignore-case t)
1932          (default (format "%S" (nth 2 copp)))
1933          (answer
1934           (read-from-minibuffer
1935            (concat
1936             (car copp)
1937             " (default " default "): ")
1938            nil nil nil nil
1939            default)))
1940     (if (and answer (not (string= answer "")))
1941         answer
1942       default)))
1943
1944 ;;; ChainOp Editor
1945
1946 (defvar ecasound-cop-edit-mode-map
1947   (let ((map (make-keymap)))
1948     (set-keymap-parent map widget-keymap)
1949     map))
1950
1951 (define-derived-mode ecasound-cop-edit-mode fundamental-mode "COP-edit"
1952   "A major mode for editing ecasound chain operators.")
1953
1954 (defun ecasound-cop-edit ()
1955   "Edit the chain operator settings of the current session interactively.
1956 This is done using the ecasound-cop widget."
1957   (interactive)
1958   (let ((cb (current-buffer))
1959         (chains (eci-cop-status)))
1960     (switch-to-buffer-other-window (generate-new-buffer "*cop-edit*"))
1961     (ecasound-cop-edit-mode)
1962     (mapc
1963      (lambda (chain)
1964        (widget-insert (format "Chain %s:\n" (car chain)))
1965        (mapc
1966         (lambda (cop)
1967           (apply 'widget-create 'ecasound-cop :buffer cb cop))
1968         (cdr chain)))
1969      chains)
1970     (widget-setup)
1971     (goto-char (point-min))))
1972
1973 (define-widget 'ecasound-cop 'default
1974   "A Chain Operator.
1975 :children is a list of ecasound-copp widgets."
1976   :convert-widget
1977   (lambda (widget)
1978     (let ((args (widget-get widget :args)))
1979       (when args
1980         (widget-put widget :tag (car args))
1981         (widget-put widget :cop-number (nth 1 args))
1982         (widget-put widget :args (cddr args))))
1983     widget)
1984   :value-create
1985   (lambda (widget)
1986     (widget-put
1987      widget :children
1988      (mapcar
1989       (lambda (copp-arg)
1990         (apply 'widget-create-child-and-convert
1991              widget '(ecasound-copp) copp-arg))
1992       (widget-get widget :args))))
1993   :format-handler
1994   (lambda (widget escape)
1995     (cond
1996      ((eq escape ?i)
1997       (widget-put
1998        widget :cop-select
1999        (widget-create-child-value
2000         widget '(ecasound-cop-select) (widget-get widget :cop-number))))))
2001   :format "%i %t\n%v")
2002
2003 (define-widget 'ecasound-cop-select 'link
2004   "Select this chain operator parameter."
2005   :help-echo "RET to select."
2006   :button-prefix ""
2007   :button-suffix ""
2008   :format "%[%v.%]"
2009   :action
2010   (lambda (widget &rest ignore)
2011     (let ((buffer (widget-get (widget-get widget :parent) :buffer)))
2012       (eci-cop-select (widget-value widget) buffer))))
2013
2014 ;;;; A Chain Operator Parameter Widget.
2015
2016 ; This is used as a component of the cop widget.
2017
2018 (define-widget 'ecasound-copp 'number
2019   "A Chain operator parameter."
2020   :action 'ecasound-copp-action
2021   :convert-widget 'ecasound-copp-convert
2022   :format "  %i %v (%t)\n"
2023   :format-handler 'ecasound-copp-format-handler
2024   :size 10)
2025
2026 (defun ecasound-copp-convert (widget)
2027   "Convert args."
2028   (let ((args (widget-get widget :args)))
2029     (when args
2030       (widget-put widget :tag (car args))
2031       (widget-put widget :copp-number (nth 1 args))
2032       (widget-put widget :value (nth 2 args))
2033       (widget-put widget :args nil)))
2034   widget)
2035
2036 (defun ecasound-copp-format-handler (widget escape)
2037   (cond
2038    ((eq escape ?i)
2039     (widget-put
2040      widget
2041      :copp-select
2042      (widget-create-child-value
2043       widget
2044       '(ecasound-copp-select)
2045       (widget-get widget :copp-number))))
2046    ((eq escape ?s)
2047     (widget-put
2048      widget
2049      :slider
2050      (widget-create-child-value
2051       widget
2052       '(slider)
2053       (string-to-number (widget-get widget :value)))))))
2054
2055 (defun ecasound-copp-action (widget &rest ignore)
2056   "Sets WIDGETs value in its associated ecasound buffer."
2057   (let ((buffer (widget-get (widget-get widget :parent) :buffer)))
2058     (if (widget-apply widget :match (widget-value widget))
2059         (progn
2060           (eci-cop-set (widget-get (widget-get widget :parent) :cop-number)
2061                        (widget-get widget :copp-number)
2062                        (widget-value widget)
2063                        buffer))
2064       (message "Invalid"))))
2065
2066 (defvar ecasound-copp-select-keymap
2067   (let ((map (copy-keymap widget-keymap)))
2068     (define-key map "+" 'ecasound-copp-increase)
2069     (define-key map "-" 'ecasound-copp-decrease)
2070     map)
2071   "Keymap used inside an copp.")
2072
2073 (defun ecasound-copp-increase (pos &optional event)
2074   (interactive "@d")
2075   ;; BUG, if we do this, the field is suddently no longer editable, why???
2076   (let ((widget (widget-get (widget-at pos) :parent)))
2077     (widget-value-set
2078      widget
2079      (+ (widget-value widget) 1))
2080     (widget-apply widget :action)
2081     (widget-setup)))
2082
2083 (defun ecasound-copp-decrease (pos &optional event)
2084   (interactive "@d")
2085   (let ((widget (widget-get (widget-at pos) :parent)))
2086     (widget-value-set
2087      widget
2088      (- (widget-value widget) 1))
2089     (widget-apply widget :action)
2090     (widget-setup)))
2091
2092 (define-widget 'ecasound-copp-select 'link
2093   "Select this chain operator parameter."
2094   :help-echo "RET to select, +/- to set in steps."
2095   :keymap ecasound-copp-select-keymap
2096   :format "%[%v%]"
2097   :action 'ecasound-copp-select-action)
2098
2099 (defun ecasound-copp-select-action (widget &rest ignore)
2100   "Selects WIDGET in its associated ecasound buffer."
2101   (let ((buffer (widget-get (widget-get (widget-get widget :parent) :parent)
2102                             :buffer)))
2103     (eci-copp-select (widget-get widget :value) buffer)))
2104
2105 (define-widget 'slider 'default
2106   "A slider."
2107   :action 'widget-slider-action
2108   :button-prefix ""
2109   :button-suffix ""
2110   :format "(%[%v%])"
2111   :keymap
2112   (let ((map (copy-keymap widget-keymap)))
2113     (define-key map "\C-m" 'widget-slider-press)
2114     (define-key map "+" 'widget-slider-increase)
2115     (define-key map "-" 'widget-slider-decrease)
2116     map)
2117   :value-create 'widget-slider-value-create
2118   :value-delete 'ignore
2119   :value-get 'widget-value-value-get
2120   :size 70
2121   :value 0)
2122
2123 (defun widget-slider-press (pos &optional event)
2124   "Invoke slider at POS."
2125   (interactive "@d")
2126   (let ((button (get-char-property pos 'button)))
2127     (if button
2128         (widget-apply-action
2129          (widget-value-set
2130           button
2131           (- pos (overlay-start (widget-get button :button-overlay))))
2132          event)
2133       (let ((command (lookup-key widget-global-map (this-command-keys))))
2134         (when (commandp command)
2135           (call-interactively command))))))
2136
2137 (defun widget-slider-increase (pos &optional event)
2138   "Increase slider at POS."
2139   (interactive "@d")
2140   (widget-slider-change pos #'+ 1 event))
2141
2142 (defun widget-slider-decrease (pos &optional event)
2143   "Decrease slider at POS."
2144   (interactive "@d")
2145   (widget-slider-change pos #'- 1 event))
2146
2147 (defun widget-slider-change (pos function value &optional event)
2148   "Change slider at POS by applying FUNCTION to old-value and VALUE."
2149   (let ((button (get-char-property pos 'button)))
2150     (if button
2151         (widget-apply-action
2152          (widget-value-set button (apply function (widget-value button) value))
2153          event)
2154       (let ((command (lookup-key widget-global-map (this-command-keys))))
2155         (when (commandp command)
2156           (call-interactively command))))))
2157
2158 (defun widget-slider-action (widget &rest ignore)
2159   "Set the current :parent value to :value."
2160   (widget-value-set (widget-get widget :parent)
2161                     (widget-value widget)))
2162
2163 (defun widget-slider-value-create (widget)
2164   "Create a sliders value."
2165   (let ((size (widget-get widget :size))
2166         (value (string-to-int (format "%.0f" (widget-get widget :value))))
2167         (from (point)))
2168     (insert-char ?\  value)
2169     (insert-char ?\| 1)
2170     (insert-char ?\  (- size value 1))))
2171
2172 \f
2173 ;;; Ecasound .ewf major mode
2174
2175 (defgroup ecasound-ewf nil
2176   "Ecasound .ewf file mode related variables and faces."
2177   :prefix "ecasound-ewf-"
2178   :group 'ecasound)
2179
2180 (defcustom ecasound-ewf-output-device "/dev/dsp"
2181   "*Default output device used for playing .ewf files."
2182   :group 'ecasound-ewf
2183   :type 'string)
2184
2185 (defface ecasound-ewf-keyword-face '((t (:foreground "IndianRed")))
2186   "The face used for highlighting keywords."
2187   :group 'ecasound-ewf)
2188
2189 (defface ecasound-ewf-time-face '((t (:foreground "Cyan")))
2190   "The face used for highlighting time information."
2191   :group 'ecasound-ewf)
2192
2193 (defface ecasound-ewf-file-face '((t (:foreground "Green")))
2194   "The face used for highlighting the filname."
2195   :group 'ecasound-ewf)
2196
2197 (defface ecasound-ewf-boolean-face '((t (:foreground "Orange")))
2198   "The face used for highlighting boolean values."
2199   :group 'ecasound-ewf)
2200
2201 (defvar ecasound-ewf-mode-map
2202   (let ((map (make-sparse-keymap)))
2203     (define-key map "\t" 'pcomplete)
2204     (define-key map "\C-c\C-p" 'ecasound-ewf-play)
2205     map)
2206   "Keymap for `ecasound-ewf-mode'.")
2207
2208 (defvar ecasound-ewf-mode-syntax-table
2209   (let ((st (make-syntax-table)))
2210     (modify-syntax-entry ?# "<" st)
2211     (modify-syntax-entry ?\n ">" st)
2212     st)
2213   "Syntax table for `ecasound-ewf-mode'.")
2214
2215 (defvar ecasound-ewf-font-lock-keywords
2216   '(("^\\s-*\\(source\\)[^=]+=\\s-*\\(.*\\)$"
2217      (1 'ecasound-ewf-keyword-face)
2218      (2 'ecasound-ewf-file-face))
2219     ("^\\s-*\\(offset\\)[^=]+=\\s-*\\([0-9.]+\\)$"
2220      (1 'ecasound-ewf-keyword-face)
2221      (2 'ecasound-ewf-time-face))
2222     ("^\\s-*\\(start-position\\)[^=]+=\\s-*\\([0-9.]+\\)$"
2223      (1 'ecasound-ewf-keyword-face)
2224      (2 'ecasound-ewf-time-face))
2225     ("^\\s-*\\(length\\)[^=]+=\\s-*\\([0-9.]+\\)$"
2226      (1 'ecasound-ewf-keyword-face)
2227      (2 'ecasound-ewf-time-face))
2228     ("^\\s-*\\(looping\\)[^=]+=\\s-*\\(true\\|false\\)$"
2229      (1 'ecasound-ewf-keyword-face)
2230      (2 'ecasound-ewf-boolean-face)))
2231   "Keyword highlighting specification for `ecasound-ewf-mode'.")
2232
2233 ;;;###autoload
2234 (define-derived-mode ecasound-ewf-mode fundamental-mode "EWF"
2235   "A major mode for editing ecasound .ewf files."
2236   (set (make-local-variable 'comment-start) "# ")
2237   (set (make-local-variable 'comment-start-skip) "#+\\s-*")
2238   (set (make-local-variable 'font-lock-defaults)
2239        '(ecasound-ewf-font-lock-keywords))
2240   (ecasound-ewf-setup-pcomplete))
2241
2242 ;;; .ewf-mode pcomplete support
2243
2244 (defun ecasound-ewf-keyword-completion-function ()
2245   (pcomplete-here
2246    (list "source" "offset" "start-position" "length" "looping")))
2247
2248 (defun pcomplete/ecasound-ewf-mode/source ()
2249   (pcomplete-here (pcomplete-entries)))
2250
2251 (defun pcomplete/ecasound-ewf-mode/offset ()
2252   (message "insert audio object at offset (seconds) [read,write]")
2253   (throw 'pcompleted t))
2254
2255 (defun pcomplete/ecasound-ewf-mode/start-position ()
2256   (message "start offset inside audio object (seconds) [read]")
2257   (throw 'pcompleted t))
2258
2259 (defun pcomplete/ecasound-ewf-mode/length ()
2260   (message "how much of audio object data is used (seconds) [read]")
2261   (throw 'pcompleted t))
2262
2263 (defun pcomplete/ecasound-ewf-mode/looping ()
2264   (pcomplete-here (list "true" "false")))
2265
2266 (defun ecasound-ewf-parse-arguments ()
2267   "Parse whitespace separated arguments in the current region."
2268   (let ((begin (save-excursion (beginning-of-line) (point)))
2269         (end (point))
2270         begins args)
2271     (save-excursion
2272       (goto-char begin)
2273       (while (< (point) end)
2274         (skip-chars-forward " \t\n=")
2275         (setq begins (cons (point) begins))
2276         (let ((skip t))
2277           (while skip
2278             (skip-chars-forward "^ \t\n=")
2279             (if (eq (char-before) ?\\)
2280                 (skip-chars-forward " \t\n=")
2281               (setq skip nil))))
2282         (setq args (cons (buffer-substring-no-properties
2283                           (car begins) (point))
2284                          args)))
2285       (cons (reverse args) (reverse begins)))))
2286
2287 (defun ecasound-ewf-setup-pcomplete ()
2288   (set (make-local-variable 'pcomplete-parse-arguments-function)
2289        'ecasound-ewf-parse-arguments)
2290   (set (make-local-variable 'pcomplete-command-completion-function)
2291        'ecasound-ewf-keyword-completion-function)
2292   (set (make-local-variable 'pcomplete-command-name-function)
2293        (lambda ()
2294          (pcomplete-arg 'first)))
2295   (set (make-local-variable 'pcomplete-arg-quote-list)
2296        (list ? )))
2297
2298 ;;; Interactive commands
2299
2300 ;; FIXME: Make it use ECI.
2301 (defun ecasound-ewf-play ()
2302   (interactive)
2303   (let ((ecasound-arguments (list "-c"
2304                                   "-i" buffer-file-name
2305                                   "-o" ecasound-ewf-output-device)))
2306     (and (buffer-modified-p)
2307          (y-or-n-p "Save file before playing? ")
2308          (save-buffer))
2309     (ecasound "*Ecasound-ewf Player*")))
2310
2311 (add-to-list 'auto-mode-alist (cons "\\.ewf$" 'ecasound-ewf-mode))
2312
2313 ;; Local variables:
2314 ;; mode: outline-minor
2315 ;; outline-regexp: ";;;;* \\|\f"
2316 ;; End:
2317
2318 (provide 'ecasound)
2319
2320 ;;; ecasound.el ends here
2321