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