1 ;;; ecasound.el --- Interactive and programmatic interface to Ecasound
3 ;; Copyright (C) 2001, 2002 Mario Lang
5 ;; Author: Mario Lang <mlang@delysid.org>
6 ;; Keywords: audio, ecasound, eci, comint, process, pcomplete
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)
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.
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.
26 ;; This file implements several aspects of ecasound use:
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
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.
38 ;; * ecasound-ewf-mode, a mode for editing .ewf files.
43 ;; You need at least ecasound 2.2.0 for this file to work properly.
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.
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")
53 ;; To set ecasound startup options use
55 ;; M-x ecasound-customize-startup RET
57 ;; Then use M-x ecasound RET to invoke an inferior ecasound process.
59 ;; For programmatic use of the ECI API, have a look at `eci-init',
60 ;; `eci-command' and in general the eci-* namespace.
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
69 ;; M-x toggle-debug-on-error RET
70 ;; and see what you can figure out. I'm happy to receive useful suggestions.
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
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
87 ;; * Bind most important interactive functions in ecasound-iam-mode-map
88 ;; (which layout to use?)
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
110 ;; * New variable ecasound-sending-command, used to prevent the background
111 ;; timer from coliding with other ECI requests.
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.
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
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
139 ;; * `ecasound-messages': variable deleted.
140 ;; * `ecasound-arguments': Now handles -d:nnn properly.
141 ;; * Many other minor tweaks and fixes.
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
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.
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.
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'.
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
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.
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.
212 ;; * Fixed `eci-command' once again, it blocked for nearly every call... :(
213 ;; * Fixed ecasound-cop-add in the ladspa case.
217 ;; * Fixed missing require.
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
227 ;; * Added `ecasound-messages' for a nice customisable interface to
228 ;; loglevels, strangely, cvs version doesnt seem to recognize
233 ;; * Created a slider widget. It's not flawless, but it works!
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.
254 Variables in this group affect inferior ecasound processes started from
255 within Emacs using the command `ecasound'.
257 See the subgroup `eci' for settings which affect the programmatic interface
262 (define-widget 'ecasound-cli-arg 'string
263 "A Custom Widget for a command-line argument."
265 :string-match 'ecasound-cli-arg-string-match
266 :match 'ecasound-cli-arg-match
268 (lambda (widget value)
269 (when (widget-apply widget :string-match value)
270 (match-string 1 value)))
272 (lambda (widget value)
273 (format (widget-apply widget :arg-format) value)))
275 (defun ecasound-cli-arg-match (widget value)
276 (when (stringp value)
277 (widget-apply widget :string-match value)))
279 (defun ecasound-cli-arg-string-match (widget value)
281 (format (concat "^" (regexp-quote (widget-get widget :arg-format)))
282 (concat "\\(" (widget-get widget :pattern) "\\)"))
285 (define-widget 'ecasound-daemon-port 'ecasound-cli-arg
286 "A Custom Widget for the --daemon-port:port argument."
288 :arg-format "--daemon-port:%s")
290 (define-widget 'ecasound-chainsetup-name 'ecasound-cli-arg
291 "A Custom Widget for the -n:chainsetup argument."
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."
298 :tag "Chainsetup name")
300 (define-widget 'ecasound-buffer-size 'ecasound-cli-arg
301 "A Custom Widget for the -b:buffer size argument."
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
315 (define-widget 'ecasound-debug-level 'set
316 "Custom widget for the -d:nnn argument."
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
330 :string-match 'ecasound-cli-arg-string-match
333 (lambda (widget value)
334 (format (widget-get widget :arg-format)
335 (number-to-string (apply #'+ (widget-apply widget :value-get)))))
337 (lambda (widget value)
338 (when (widget-apply widget :string-match value)
339 (let ((level (string-to-number (match-string 1 value)))
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)
346 (mapcar (lambda (elem)
347 (when (eq (/ level elem) 1)
348 (setq level (- level elem))
351 (define-widget 'ecasound-args 'set
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)"
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."
367 (const :tag "Open outputs for updating"
368 :doc "Ecasound opens all outputs - if target format allows it - in readwrite mode."
370 (repeat :tag "Others" :inline t (string :tag "Argument"))))
372 (defcustom ecasound-arguments '("-c" "-d:259" "--daemon" "--daemon-port:2868"
374 "*Command line arguments used when starting an ecasound process."
376 :type 'ecasound-args)
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)))
383 (match-string 1 (car elem)))))
385 (defun ecasound-customize-startup ()
386 "Customize ecasound startup arguments."
388 (customize-variable 'ecasound-arguments))
390 (defcustom ecasound-program "/home/mlang/bin/ecasound"
391 "*Ecasound's executable.
392 This program is executed when the user invokes \\[ecasound]."
396 (defcustom ecasound-prompt-regexp "^ecasound[^>]*> "
397 "Regexp to use to match the prompt."
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."
408 (defcustom ecasound-error-hook nil
409 "*Called whenever a ECI error happens."
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."
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))
424 (defface ecasound-error-face '((t (:foreground "White" :background "Red")))
425 "Face used to highlight errors."
428 (defcustom ecasound-timer-flag t
429 "*If non-nil, fetch status information in background."
433 (defcustom ecasound-timer-interval 2
434 "*Defines how often status information should be fetched."
438 (defcustom ecasound-mode-line-format
440 mode-line-frame-identification
441 mode-line-buffer-identification
442 eci-engine-status " "
445 (:eval (mode-line-mode-name))
450 (line-number-mode "L%l--")
451 (column-number-mode "C%c--")
454 "*Mode Line Format used in `ecasound-iam-mode'."
460 (cons integer string)
461 (list :tag "Evaluate" (const :value :eval) sexp)
464 (defcustom ecasound-header-line-format nil
465 "*If non-nil, defines the header line format for `ecasound-iam-mode' buffers."
469 (defvar ecasound-sending-command nil
470 "Non-nil if `eci-command' is running.")
472 (make-variable-buffer-local
473 (defvar ecasound-daemon nil
474 "If non-nil, this variable holds the buffer object of a daemon channel."))
476 (make-variable-buffer-local
477 (defvar ecasound-parent nil
478 "If non-nil, this variable holds the buffer object of a daemon parent."))
480 (make-variable-buffer-local
481 (defvar ecasound-daemon-timer nil))
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)
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)
537 ecasound-iam-cs-menu ecasound-iam-mode-map
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]
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]
558 (easy-menu-add ecasound-iam-cs-menu ecasound-iam-mode-map)
560 ecasound-iam-c-menu ecasound-iam-mode-map
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]
571 (easy-menu-add ecasound-iam-c-menu ecasound-iam-mode-map)
573 ecasound-iam-cop-menu ecasound-iam-mode-map
574 "Chain Operator menu."
576 ["Add..." eci-cop-add (> (length (eci-c-selected)) 0)]
577 ["Select..." eci-cop-select t]
578 ["Edit..." ecasound-cop-edit t]
580 ["Select parameter..." eci-copp-select t]
581 ["Get parameter value" eci-copp-get t]
582 ["Set parameter value..." eci-copp-set t]
584 (easy-menu-add ecasound-iam-c-menu ecasound-iam-mode-map)
586 ecasound-iam-ai-menu ecasound-iam-mode-map
587 "Audio Input Object menu."
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]
594 ["Attach" eci-ai-attach t]
595 ["Remove" eci-ai-remove t]
596 ["Forward..." eci-ai-forward t]
597 ["Rewind..." eci-ai-rewind t]
599 (easy-menu-add ecasound-iam-ai-menu ecasound-iam-mode-map)
601 ecasound-iam-ao-menu ecasound-iam-mode-map
602 "Audio Output Object menu."
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]
610 ["Attach" eci-ao-attach t]
611 ["Remove" eci-ao-remove t]
612 ["Forward..." eci-ao-forward t]
613 ["Rewind..." eci-ao-rewind t]
615 (easy-menu-add ecasound-iam-ao-menu ecasound-iam-mode-map)
618 ecasound-menu global-map
621 ["Get session" ecasound t]
623 ["Normalize..." ecasound-normalize t]
624 ["Signalview..." ecasound-signalview t]
626 ["Customize startup..." ecasound-customize-startup t]
628 (easy-menu-add ecasound-menu global-map)
630 (make-variable-buffer-local
631 (defvar ecasound-mode-string nil))
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))
643 (defun ecasound-mode-line-cop-list (handle)
644 (let ((list (eci-cop-list handle))
645 (sel (1- (eci-cop-selected handle)))
647 (dotimes (i (length list) str)
648 (setq str (format "%s%s%s%s"
650 (if (= i sel) "*" "")
652 (if (= i (length list)) "" ","))))))
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)))
659 (defun ecasound-kill-timer ()
660 "Cancels the background timer.
661 Use this if you want to stop background information fetching."
663 (when ecasound-daemon-timer
664 (cancel-timer ecasound-daemon-timer)))
666 (defun ecasound-kill-daemon ()
667 "Terminate the daemon channel."
669 (ecasound-kill-timer)
670 (when (ecasound-daemon-p)
671 (kill-buffer ecasound-daemon)))
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
682 " [" (ecasound-position-to-string
683 (eci-cs-get-position ecasound-daemon))
684 "/" (ecasound-position-to-string
685 (eci-cs-get-length ecasound-daemon))
690 (eci-cs-selected ecasound-daemon)
691 " [" (if (eci-cs-is-valid-p ecasound-daemon)
694 (mapconcat 'identity (eci-c-list ecasound-daemon) ",")
697 (eci-c-selected ecasound-daemon) ","))))))))
699 (defun ecasound-setup-timer ()
700 (when (and ecasound-timer-flag (ecasound-daemon-p))
701 (setq ecasound-daemon-timer
703 0 ecasound-timer-interval
704 'ecasound-update-mode-line
707 (make-variable-buffer-local
708 (defvar eci-int-output-mode-wellformed-flag nil
709 "Indicates if int-output-mode-wellformed was successfully initialized."))
711 (make-variable-buffer-local
712 (defvar eci-engine-status nil
713 "If non-nil, a string describing the engine-status."))
715 (make-variable-buffer-local
716 (defvar eci-cs-selected nil
717 "If non-nil, a string describing the selected chain setup."))
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'.
730 \(Type \\[describe-mode] in the ecasound buffer for a list of commands.)"
733 (and current-prefix-arg
734 (read-buffer "Ecasound buffer: " "*ecasound*"))))
736 (setq buffer "*ecasound*"))
737 (if (not (comint-check-proc buffer))
747 ;; Flush process output
748 (while (accept-process-output
749 (get-buffer-process (current-buffer))
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))
762 (pop-to-buffer buffer)))
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
773 (cons "localhost" (ecasound-daemon-port))))
775 (setq comint-input-sender 'ecasound-network-send
776 eci-int-output-mode-wellformed-flag t
778 (set (make-variable-buffer-local 'comint-highlight-prompt) nil)
779 (setq comint-output-filter-functions '(ecasound-output-filter))
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")))))
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."
790 (save-excursion (goto-char comint-last-input-end) (forward-line -1)
791 (unless (looking-at ecasound-prompt-regexp)
792 (error "Assumed ecasound-prompt"))
794 comint-last-output-start))
796 (make-variable-buffer-local
797 (defvar eci-last-command nil
798 "Last command sent to the ecasound process."))
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."))
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))))
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"))
822 (defcustom ecasound-last-command-alist
823 '(("int-output-mode-wellformed" .
824 (setq eci-int-output-mode-wellformed-flag t))
826 (setq ecasound-iam-commands value))
828 (setq eci-map-cop-list (eci-process-map-list value)))
830 (setq eci-map-ladspa-list (eci-process-map-list value)))
832 (setq eci-map-ctrl-list (eci-process-map-list value)))
834 (setq eci-map-preset-list (eci-process-map-list value)))
836 (eci-process-cop-status value))
838 (setq eci-engine-status value))
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
846 :type '(alist :key-type (string :tag "Command")
847 :value-type (sexp :tag "Lisp Expression")))
849 (defcustom ecasound-type-alist
851 ("i" . (string-to-number value))
852 ("li" . (string-to-number value))
853 ("f" . (string-to-number 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."
861 :type '(alist :key-type (string :tag "Type")
862 :value-type (sexp :tag "Lisp Expression")))
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
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)))
875 (setq value (eval (cdar tcode)))
876 (error "Return type '%s' not defined in `ecasound-type-alist'" type))
877 (setq eci-return-value value
884 (make-variable-buffer-local
885 (defvar eci-return-type nil
886 "The return type of the last received return value as a string."))
888 (make-variable-buffer-local
889 (defvar eci-return-value nil
890 "The last received return value as a string."))
892 (make-variable-buffer-local
893 (defvar eci-result nil
894 "The last received return value as a Lisp Object."))
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)))))
904 (let (type value (end (copy-marker end)))
906 (while (re-search-forward
907 "\\([0-9]\\{1,3\\}\\) \\([0-9]\\{1,5\\}\\)\\( \\(.*\\)\\)?\n"
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
915 (if (> (- (point-max) (point)) msgsize)
917 (forward-char msgsize)
918 (if (not (save-match-data
920 "\\(\n\n\\|
\r\n
\r\n\\)")))
921 (error "Malformed ECI message")
924 (when (= msgsize (length msg))
925 (if (and (= loglevel 256)
926 (string= return-type "e"))
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)
935 (setq ecasound-last-parse-start (point))
936 (if (not (= loglevel 256))
937 (run-hook-with-args 'ecasound-message-hook loglevel msg)
939 type (if (string-match "\\(.*\\)
\r" return-type)
940 (match-string 1 return-type)
943 (ecasound-process-result type value)))))))
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
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
959 (let ((sym (intern (format "eci-%S" name)))
960 (pcmpl-sym (intern (format "pcomplete/ecasound-iam-mode/%S" name)))
963 `(format ,(format "%S %s"
964 name (mapconcat #'caddr args ","))
967 `(if (or (stringp ,(car arg))
968 (numberp ,(car arg)))
970 (mapconcat #'identity ,(car arg) ",")))
974 cache cache-doc pcmpl aliases)
975 (while (keywordp (car 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)))
982 (when (and (not (eq aliases nil))
983 (not (consp aliases)))
984 (setq aliases (list aliases)))
987 `(make-variable-buffer-local
988 (defvar ,cache ,@(if cache-doc (list nil cache-doc) (list nil)))))
990 ,(if args (append (mapcar #'car args) `(&optional buffer-or-process))
991 `(&optional buffer-or-process))
993 ,(if args `(interactive
995 (mapcar (lambda (x) (when x (setq done t)))
996 (mapcar #'stringp (mapcar #'cadr args)))
998 (mapconcat #'identity (mapcar #'cadr args) "\n")
999 `(list ,@(mapcar #'cadr args))))
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
1016 (lambda (alias) `(defalias ',(intern (format "eci-%S" alias))
1020 ,(if (and (eq pcmpl 'doc) (stringp doc) (not (string= doc "")))
1021 `(defun ,pcmpl-sym ()
1023 (throw 'pcompleted t))
1024 `(defun ,pcmpl-sym ()
1028 `(defalias ',(intern (format "pcomplete/ecasound-iam-mode/%S" alias))
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.
1037 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1039 Use `eci-map-cop-list' to fill this variable with data.")
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.
1046 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1048 Use `eci-map-ctrl-list' to fill this list with data.")
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.
1055 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1057 Use `eci-map-ladspa-list' to fill this list with data.")
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.
1064 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1066 Use `eci-map-preset-list' to fill this list with data.")
1068 ;;; Ecasound-iam-mode pcomplete functions
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)
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)
1079 (pcomplete-arg 'first)))
1080 (set (make-local-variable 'pcomplete-parse-arguments-function)
1081 'ecasound-iam-pcomplete-parse-arguments))
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)))
1091 (while (< (point) end)
1092 (skip-chars-forward " \t\n,:")
1093 (setq begins (cons (point) begins))
1096 (skip-chars-forward "^ \t\n,:")
1097 (if (eq (char-before) ?\\)
1098 (skip-chars-forward " \t\n,:")
1100 (setq args (cons (buffer-substring-no-properties
1101 (car begins) (point))
1103 (cons (reverse args) (reverse begins)))))
1105 (defun ecasound-input-file-or-device ()
1106 "Return a list of possible completions for input device name."
1112 (concat "^" (regexp-quote pcomplete-stub)) elt)
1114 (list "alsa" "alsahw" "alsalb" "alsaplugin"
1115 "arts" "loop" "null" "stdin")))
1116 (pcomplete-entries)))
1120 (defun eci-map-find-args (arg map)
1121 "Return the argument specification for ARG in MAP."
1124 (if (string= (nth 1 (car map)) arg)
1125 (setq result (nthcdr 3 (car map))
1127 (setq map (cdr map))))
1130 (defun ecasound-echo-arg (arg)
1131 "Display a chain operator parameter description from a eci-map-*-list
1134 (let ((type (nth 5 arg)))
1135 (message "%s%s%s, default %S%s%s"
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))
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")))
1148 ;;; ECI --- The Ecasound Control Interface
1151 "Ecasound Control Interface."
1154 (defcustom eci-program (or (getenv "ECASOUND") "ecasound")
1155 "*Program to invoke when doing `eci-init'."
1157 :type '(choice string (cons string string)))
1159 (defcustom eci-arguments '("-c" "-D" "-d:256")
1160 "*Arguments used by `eci-init'."
1162 :type 'ecasound-args)
1164 (defvar eci-hide-output nil
1165 "If non-nil, `eci-command' will remove the output generated.")
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))
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
1181 The caller is responsible for terminating the subprocess at some point."
1190 (while (accept-process-output (get-buffer-process (current-buffer)) 1))
1191 (if (eci-command "int-output-mode-wellformed")
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
1199 (switch-to-buffer (eci-init)))
1201 (defun ecasound-find-buffer (buffer-or-process)
1203 ((bufferp 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)))
1210 (t (error "Could not determine suitable ecasound buffer"))))
1212 (defun ecasound-find-parent (buffer-or-process)
1213 (with-current-buffer (ecasound-find-buffer buffer-or-process)
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))
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))
1242 (if eci-int-output-mode-wellformed-flag
1244 ;; Backward compatibility. Just return the string
1245 (buffer-substring-no-properties here (save-excursion
1246 ; Strange hack to avoid fields
1249 (if (not (= here (point)))
1252 (if moving (goto-char (point-max)))
1253 (when (and eci-hide-output result)
1254 (ecasound-delete-last-in-and-output))
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"))
1261 ;;; ECI commands implemented as lisp functions
1263 (defeci int-cmd-list ()
1265 :cache ecasound-iam-commands
1266 :cache-doc "Available Ecasound IAM commands.")
1272 (defeci cs-add ((chainsetup "sChainsetup to add: " "%s"))
1273 "Adds a new chainsetup with name `name`."
1276 (defeci cs-connect ()
1277 "Connect currently selected chainsetup to engine."
1280 (defeci cs-connected ()
1281 "Returns the name of currently connected chainsetup."
1284 (defeci cs-disconnect ()
1285 "Disconnect currently connected chainsetup."
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")))
1295 (defeci cs-get-length ()
1299 (defeci cs-get-length-samples ()
1301 :alias get-length-samples)
1303 (defeci cs-get-position ()
1305 :alias (cs-getpos getpos get-position))
1307 (defeci cs-index-select ((index "nChainsetup index: " "%d"))
1311 (defeci cs-is-valid ()
1312 "Whether currently selected chainsetup is valid (=can be connected)?"
1314 (let ((val (eci-command "cs-is-valid" buffer-or-process)))
1316 (message (format "Chainsetup is%s valid" (if (= val 0) "" " not"))))
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)
1324 (otherwise (error "Unexcpected return value from cs-is-valid"))))
1327 "Returns a list of all chainsetups."
1329 (let ((val (eci-command "cs-list" buffer-or-process)))
1331 (message (concat "Available chainsetups: "
1332 (mapconcat #'identity val ", "))))
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)))
1340 (defeci cs-remove ()
1341 "Removes currently selected chainsetup."
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."
1355 (defeci cs-save-as ((filename "FChainsetup filename: " "%s"))
1356 "Saves currently selected chainsetup to file FILENAME."
1357 :pcomplete (pcomplete-here (pcomplete-entries)))
1359 (defeci cs-selected ()
1360 "Returns the name of currently selected chainsetup."
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)))))
1366 (message (format "Selected chainsetup: %s" val)))
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 ','.")
1376 "Clear selected chains by removing all chain operators and controllers.
1377 Doesn't change how chains are connected to inputs and outputs."
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."
1389 (setq current (completing-read prompt (mapcar #'list avail)))
1391 (setq result (cons current result)
1392 avail (delete current avail)))
1396 ((chains (ecasound-read-list "Chain to deselect: " (eci-c-selected)) "%s"))
1398 :pcomplete (while (pcomplete-here (eci-c-selected))))
1401 "Returns a list of all chains.")
1404 "Toggle chain muting. When chain is muted, all data that goes
1408 (defeci c-select ((chains (ecasound-read-list "Chain: " (eci-c-list)) "%s"))
1409 "Selects chains. Other chains are automatically deselected."
1412 (defeci c-selected ()
1414 (let ((val (eci-command "c-selected" buffer-or-process)))
1417 (message "No selected chains")
1418 (message (concat "Selected chains: "
1419 (mapconcat #'identity val ", ")))))
1422 (defeci c-select-all ()
1423 "Selects all chains."
1428 (completing-read "Chainsetup: " (mapcar #'list (eci-cs-list)))
1431 :pcomplete (pcomplete-here (eci-hide-output eci-cs-list)))
1435 (let ((file (read-file-name "Input filename: ")))
1436 (if (file-exists-p file)
1437 (expand-file-name file)
1440 "Adds a new input object."
1441 :pcomplete (pcomplete-here (ecasound-input-file-or-device)))
1443 (defeci ai-attach ()
1444 "Attaches the currently selected audio input object to all selected chains."
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)."
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)."
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."
1480 (defeci ai-remove ()
1481 "Removes the currently selected audio input object from the chainsetup."
1483 (defeci ao-remove ()
1484 "Removes the currently selected audio output object from the chainsetup."
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)))
1495 (defeci ai-selected ()
1496 "Returns the name of the currently selected audio input object."
1499 (defeci ao-add ((filename "FOutput filename: " "%s"))
1501 :pcomplete (pcomplete-here (ecasound-input-file-or-device)))
1503 (defeci ao-add-default)
1505 (defeci ao-attach ()
1506 "Attaches the currently selected audio output object to all selected chains."
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)."
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."
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)."
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)))
1549 (defeci ao-selected ()
1550 "Returns the name of the currently selected audio output object."
1553 (defeci engine-status ()
1554 "Returns a string describing the engine status
1555 \(running, stopped, finished, error, not ready)."
1557 (with-current-buffer (ecasound-find-parent buffer-or-process)
1558 (setq eci-engine-status (eci-command "engine-status" buffer-or-process))))
1560 (defmacro ecasound-complete-cop-map (map)
1561 (let ((m (intern (format "eci-map-%S-list" map))))
1564 ((= pcomplete-last 2)
1565 (pcomplete-next-arg)
1567 (sort (mapcar (lambda (elt) (nth 1 elt))
1568 (eci-hide-output ,m))
1570 ((> pcomplete-last 2)
1572 (nth (- pcomplete-last 3)
1574 (pcomplete-arg -1) (eci-hide-output ,m)))))))))
1578 (if current-prefix-arg
1579 (read-string "Chainop to add: " "-")
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)))
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) ","))))
1604 ((= pcomplete-last 1)
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)
1618 (nth (- pcomplete-last 2)
1620 (substring (pcomplete-arg) 1)
1621 (eci-hide-output eci-map-cop-list))))))
1622 (throw 'pcompleted t)))
1629 ((index "nChainop to select: " "%d")))
1631 (defeci cop-selected)
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."
1643 (if current-prefix-arg
1644 (read-string "Controller to add: " "-")
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) ","))))
1655 ((index "nController to select: " "%d")))
1658 ((index "nChainop parameter to select: " "%d")))
1663 ((value "nValue for Chain operator parameter: " "%f")))
1667 (defun eci-example ()
1668 "Implements the example given in the ECI documentation."
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)
1680 (eci-command "start")
1682 (while (and (string= (eci-engine-status) "running")
1683 (< (eci-get-position) 15))
1684 (eci-copp-set (+ (eci-copp-get) 500))
1686 (eci-command "stop")
1688 (message (concat "Chain operator status: "
1689 (eci-command "cop-status")))))
1691 (defun eci-make-temp-file-name (suffix)
1692 (concat (make-temp-name
1693 (expand-file-name "emacs-eci" temporary-file-directory))
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 "")))
1704 (defconst ecasound-signalview-clipped-threshold (- 1.0 (/ 1.0 16384)))
1706 (defconst ecasound-signalview-bar-length 55)
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"
1714 (% (round (floor secs)) 60)
1715 (* (- secs (floor secs)) 100))))
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 ?.))))
1723 (substring str idx)))))
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."
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)
1737 (ecasound-read-from-minibuffer "Output" "null")))
1738 (let* (;; THis saves time
1739 (ecasound-parse-cleanup-buffer nil)
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*"))
1753 (dotimes (ch channels)
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
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)
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))
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)))
1796 (fit-window-to-buffer))
1797 (goto-char (point-max))
1798 (let ((pos (point)))
1803 (assoc "Volume analysis"
1805 (eci-cop-status handle)))))))
1808 (fit-window-to-buffer)))
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")))
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)
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)))
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")
1832 (eci-copp-set (* gainfactor 100))
1833 (eci-cs-connect) (eci-run) (eci-cs-disconnect)
1835 (if (file-exists-p tmpfile)
1836 (delete-file tmpfile)))))
1838 ;;; Utility functions for converting strings to data-structures.
1840 (defvar eci-cop-status-header
1841 "### Chain operator status (chainsetup '\\([^']+\\)') ###\n")
1843 (defun eci-process-cop-status (string)
1845 (insert string) (goto-char (point-min))
1846 (when (re-search-forward eci-cop-status-header nil t)
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))
1858 "\\[\\([0-9]+\\)\\] \\(.*\\) \\([0-9.-]+\\)$"
1860 (list (match-string-no-properties 2 elt)
1861 (string-to-number (match-string 1 elt))
1862 (string-to-number (match-string 3 elt)))))
1864 (match-string-no-properties 3) ", "))))
1865 (if (looking-at "\tStatus info:\n")
1873 (progn (forward-line 1) (point))
1874 (or (re-search-forward "\n\n" nil t)
1876 (setq chain (cons (append (list name n) args) chain))))
1877 (setq result (cons (reverse (append chain (list c))) result))))
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."
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))
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)))
1900 ((string= (nth 7 elt) "1")
1902 ((string= (nth 8 elt) "1")
1904 ((string= (nth 9 elt) "1")
1906 ((string= (nth 10 elt) "1")
1911 (mapcar (lambda (str) (split-string str ","))
1912 (split-string string "\n"))))
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\"."
1921 (defeci cop-register)
1922 (defeci preset-register)
1923 (defeci ctrl-register)
1927 (defeci ladspa-register)
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)))
1934 (read-from-minibuffer
1937 " (default " default "): ")
1940 (if (and answer (not (string= answer "")))
1946 (defvar ecasound-cop-edit-mode-map
1947 (let ((map (make-keymap)))
1948 (set-keymap-parent map widget-keymap)
1951 (define-derived-mode ecasound-cop-edit-mode fundamental-mode "COP-edit"
1952 "A major mode for editing ecasound chain operators.")
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."
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)
1964 (widget-insert (format "Chain %s:\n" (car chain)))
1967 (apply 'widget-create 'ecasound-cop :buffer cb cop))
1971 (goto-char (point-min))))
1973 (define-widget 'ecasound-cop 'default
1975 :children is a list of ecasound-copp widgets."
1978 (let ((args (widget-get widget :args)))
1980 (widget-put widget :tag (car args))
1981 (widget-put widget :cop-number (nth 1 args))
1982 (widget-put widget :args (cddr args))))
1990 (apply 'widget-create-child-and-convert
1991 widget '(ecasound-copp) copp-arg))
1992 (widget-get widget :args))))
1994 (lambda (widget escape)
1999 (widget-create-child-value
2000 widget '(ecasound-cop-select) (widget-get widget :cop-number))))))
2001 :format "%i %t\n%v")
2003 (define-widget 'ecasound-cop-select 'link
2004 "Select this chain operator parameter."
2005 :help-echo "RET to select."
2010 (lambda (widget &rest ignore)
2011 (let ((buffer (widget-get (widget-get widget :parent) :buffer)))
2012 (eci-cop-select (widget-value widget) buffer))))
2014 ;;;; A Chain Operator Parameter Widget.
2016 ; This is used as a component of the cop widget.
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
2026 (defun ecasound-copp-convert (widget)
2028 (let ((args (widget-get widget :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)))
2036 (defun ecasound-copp-format-handler (widget escape)
2042 (widget-create-child-value
2044 '(ecasound-copp-select)
2045 (widget-get widget :copp-number))))
2050 (widget-create-child-value
2053 (string-to-number (widget-get widget :value)))))))
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))
2060 (eci-cop-set (widget-get (widget-get widget :parent) :cop-number)
2061 (widget-get widget :copp-number)
2062 (widget-value widget)
2064 (message "Invalid"))))
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)
2071 "Keymap used inside an copp.")
2073 (defun ecasound-copp-increase (pos &optional event)
2075 ;; BUG, if we do this, the field is suddently no longer editable, why???
2076 (let ((widget (widget-get (widget-at pos) :parent)))
2079 (+ (widget-value widget) 1))
2080 (widget-apply widget :action)
2083 (defun ecasound-copp-decrease (pos &optional event)
2085 (let ((widget (widget-get (widget-at pos) :parent)))
2088 (- (widget-value widget) 1))
2089 (widget-apply widget :action)
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
2097 :action 'ecasound-copp-select-action)
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)
2103 (eci-copp-select (widget-get widget :value) buffer)))
2105 (define-widget 'slider 'default
2107 :action 'widget-slider-action
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)
2117 :value-create 'widget-slider-value-create
2118 :value-delete 'ignore
2119 :value-get 'widget-value-value-get
2123 (defun widget-slider-press (pos &optional event)
2124 "Invoke slider at POS."
2126 (let ((button (get-char-property pos 'button)))
2128 (widget-apply-action
2131 (- pos (overlay-start (widget-get button :button-overlay))))
2133 (let ((command (lookup-key widget-global-map (this-command-keys))))
2134 (when (commandp command)
2135 (call-interactively command))))))
2137 (defun widget-slider-increase (pos &optional event)
2138 "Increase slider at POS."
2140 (widget-slider-change pos #'+ 1 event))
2142 (defun widget-slider-decrease (pos &optional event)
2143 "Decrease slider at POS."
2145 (widget-slider-change pos #'- 1 event))
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)))
2151 (widget-apply-action
2152 (widget-value-set button (apply function (widget-value button) value))
2154 (let ((command (lookup-key widget-global-map (this-command-keys))))
2155 (when (commandp command)
2156 (call-interactively command))))))
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)))
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))))
2168 (insert-char ?\ value)
2170 (insert-char ?\ (- size value 1))))
2173 ;;; Ecasound .ewf major mode
2175 (defgroup ecasound-ewf nil
2176 "Ecasound .ewf file mode related variables and faces."
2177 :prefix "ecasound-ewf-"
2180 (defcustom ecasound-ewf-output-device "/dev/dsp"
2181 "*Default output device used for playing .ewf files."
2182 :group 'ecasound-ewf
2185 (defface ecasound-ewf-keyword-face '((t (:foreground "IndianRed")))
2186 "The face used for highlighting keywords."
2187 :group 'ecasound-ewf)
2189 (defface ecasound-ewf-time-face '((t (:foreground "Cyan")))
2190 "The face used for highlighting time information."
2191 :group 'ecasound-ewf)
2193 (defface ecasound-ewf-file-face '((t (:foreground "Green")))
2194 "The face used for highlighting the filname."
2195 :group 'ecasound-ewf)
2197 (defface ecasound-ewf-boolean-face '((t (:foreground "Orange")))
2198 "The face used for highlighting boolean values."
2199 :group 'ecasound-ewf)
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)
2206 "Keymap for `ecasound-ewf-mode'.")
2208 (defvar ecasound-ewf-mode-syntax-table
2209 (let ((st (make-syntax-table)))
2210 (modify-syntax-entry ?# "<" st)
2211 (modify-syntax-entry ?\n ">" st)
2213 "Syntax table for `ecasound-ewf-mode'.")
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'.")
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))
2242 ;;; .ewf-mode pcomplete support
2244 (defun ecasound-ewf-keyword-completion-function ()
2246 (list "source" "offset" "start-position" "length" "looping")))
2248 (defun pcomplete/ecasound-ewf-mode/source ()
2249 (pcomplete-here (pcomplete-entries)))
2251 (defun pcomplete/ecasound-ewf-mode/offset ()
2252 (message "insert audio object at offset (seconds) [read,write]")
2253 (throw 'pcompleted t))
2255 (defun pcomplete/ecasound-ewf-mode/start-position ()
2256 (message "start offset inside audio object (seconds) [read]")
2257 (throw 'pcompleted t))
2259 (defun pcomplete/ecasound-ewf-mode/length ()
2260 (message "how much of audio object data is used (seconds) [read]")
2261 (throw 'pcompleted t))
2263 (defun pcomplete/ecasound-ewf-mode/looping ()
2264 (pcomplete-here (list "true" "false")))
2266 (defun ecasound-ewf-parse-arguments ()
2267 "Parse whitespace separated arguments in the current region."
2268 (let ((begin (save-excursion (beginning-of-line) (point)))
2273 (while (< (point) end)
2274 (skip-chars-forward " \t\n=")
2275 (setq begins (cons (point) begins))
2278 (skip-chars-forward "^ \t\n=")
2279 (if (eq (char-before) ?\\)
2280 (skip-chars-forward " \t\n=")
2282 (setq args (cons (buffer-substring-no-properties
2283 (car begins) (point))
2285 (cons (reverse args) (reverse begins)))))
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)
2294 (pcomplete-arg 'first)))
2295 (set (make-local-variable 'pcomplete-arg-quote-list)
2298 ;;; Interactive commands
2300 ;; FIXME: Make it use ECI.
2301 (defun ecasound-ewf-play ()
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? ")
2309 (ecasound "*Ecasound-ewf Player*")))
2311 (add-to-list 'auto-mode-alist (cons "\\.ewf$" 'ecasound-ewf-mode))
2314 ;; mode: outline-minor
2315 ;; outline-regexp: ";;;;* \\|
\f"
2320 ;;; ecasound.el ends here