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