1
;;; ecasound.el --- Interactive and programmatic interface to Ecasound
3
;; Copyright (C) 2001, 2002, 2003 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 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.
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
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
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
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
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.
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
130
;; * New variable ecasound-sending-command, used to prevent the background
131
;; timer from coliding with other ECI requests.
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.
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
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
159
;; * `ecasound-messages': variable deleted.
160
;; * `ecasound-arguments': Now handles -d:nnn properly.
161
;; * Many other minor tweaks and fixes.
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
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.
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.
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'.
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
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.
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.
232
;; * Fixed `eci-command' once again, it blocked for nearly every call... :(
233
;; * Fixed ecasound-cop-add in the ladspa case.
237
;; * Fixed missing require.
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
247
;; * Added `ecasound-messages' for a nice customisable interface to
248
;; loglevels, strangely, cvs version doesnt seem to recognize
253
;; * Created a slider widget. It's not flawless, but it works!
265
(defgroup ecasound nil
266
"Ecasound is a software package designed for multitrack audio processing.
267
It can be used for simple tasks like audio playback, recording and format
268
conversions, as well as for multitrack effect processing, mixing, recording
269
and signal recycling. Ecasound supports a wide range of audio inputs, outputs
270
and effect algorithms. Effects and audio objects can be combined in various
271
ways, and their parameters can be controlled by operator objects like
272
oscillators and MIDI-CCs.
274
Variables in this group affect inferior ecasound processes started from
275
within Emacs using the command `ecasound'.
277
See the subgroup `eci' for settings which affect the programmatic interface
282
(define-widget 'ecasound-cli-arg 'string
283
"A Custom Widget for a command-line argument."
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)))
293
(defun ecasound-cli-arg-match (widget value)
294
(when (stringp value)
295
(widget-apply widget :string-match value)))
297
(defun ecasound-cli-arg-string-match (widget value)
299
(format (concat "^" (regexp-quote (widget-get widget :arg-format)))
300
(concat "\\(" (widget-get widget :pattern) "\\)"))
303
(define-widget 'ecasound-daemon-port 'ecasound-cli-arg
304
"A Custom Widget for the --daemon-port:port argument."
306
:arg-format "--daemon-port:%s")
308
(define-widget 'ecasound-chainsetup-name 'ecasound-cli-arg
309
"A Custom Widget for the -n:chainsetup argument."
311
:doc "Sets the name of chainsetup.
312
If not specified, defaults either to \"command-line-setup\" or to the file
313
name from which chainsetup was loaded. Whitespaces are not allowed."
316
:tag "Chainsetup name")
318
(define-widget 'ecasound-buffer-size 'ecasound-cli-arg
319
"A Custom Widget for the -b:buffer size argument."
321
:doc "Sets the size of buffer in samples (must be an exponent of 2).
322
This is quite an important option. For real-time processing, you should set
323
this as low as possible to reduce the processing delay. Some machines can
324
handle buffer values as low as 64 and 128. In some circumstances (for
325
instance when using oscillator envelopes) small buffer sizes will make
326
envelopes act more smoothly. When not processing in real-time (all inputs
327
and outputs are normal files), values between 512 - 4096 often give better
333
(define-widget 'ecasound-debug-level 'set
334
"Custom widget for the -d:nnn argument."
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
348
:string-match 'ecasound-cli-arg-string-match
351
(lambda (widget value)
352
(format (widget-get widget :arg-format)
353
(number-to-string (apply #'+ (widget-apply widget :value-get)))))
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)
363
(mapcar (lambda (elem)
364
(when (eq (/ level elem) 1)
365
(setq level (- level elem))
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")
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.
381
Any 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."
385
(repeat :tag "Others" :inline t (string :tag "Argument"))))
387
(defcustom ecasound-arguments '("-c" "-D" "-d:259"
388
"--daemon" "--daemon-port:2868"
390
"*Command line arguments used when starting an ecasound process."
392
:type 'ecasound-args)
394
(defun ecasound-customize-startup ()
395
"Customize ecasound startup arguments."
397
(customize-variable 'ecasound-arguments))
399
(defcustom ecasound-program (executable-find "ecasound")
400
"*Ecasound's executable.
401
This program is executed when the user invokes \\[ecasound]."
405
(defcustom ecasound-prompt-regexp "^ecasound[^>]*> "
406
"Regexp to use to match the prompt."
410
(defcustom ecasound-parse-cleanup-buffer t
411
"*Indicates if `ecasound-output-filter' should cleanup the buffer.
412
This means the loglevel, msgsize and return type will get removed if
413
parsed successfully."
417
(defcustom ecasound-error-hook nil
418
"*Called whenever a ECI error happens."
422
(defcustom ecasound-message-hook '(ecasound-print-message)
423
"*Hook called whenever a message except loglevel 256 (eci) is received.
424
Arguments are LOGLEVEL and STRING."
428
(defun ecasound-print-message (level msg)
429
"Simple function which prints every message regardless which loglevel.
430
Argument LEVEL is the debug level."
431
(message "Ecasound (%d): %s" level msg))
433
(defface ecasound-error-face '((t (:foreground "White" :background "Red")))
434
"Face used to highlight errors."
437
(defcustom ecasound-timer-flag t
438
"*If non-nil, fetch status information in background."
442
(defcustom ecasound-timer-interval 2
443
"*Defines how often status information should be fetched."
447
(defcustom ecasound-mode-line-format
448
(unless (featurep 'xemacs) ;; mode-line-format seems to differ quite a lot
450
mode-line-frame-identification
451
mode-line-buffer-identification
452
eci-engine-status " "
455
(:eval (mode-line-mode-name))
460
(line-number-mode "L%l--")
461
(column-number-mode "C%c--")
464
"*Mode Line Format used in `ecasound-iam-mode'."
470
(cons integer string)
471
(list :tag "Evaluate" (const :value :eval) sexp)
474
(defcustom ecasound-header-line-format nil
475
"*If non-nil, defines the header line format for `ecasound-iam-mode' buffers."
479
(defvar ecasound-sending-command nil
480
"Non-nil if `eci-command' is running.")
482
(make-variable-buffer-local
483
(defvar ecasound-daemon nil
484
"If non-nil, this variable holds the buffer object of a daemon channel."))
486
(make-variable-buffer-local
487
(defvar ecasound-parent nil
488
"If non-nil, this variable holds the buffer object of a daemon parent."))
490
(make-variable-buffer-local
491
(defvar ecasound-daemon-timer nil))
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)
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)
551
ecasound-iam-cs-menu ecasound-iam-mode-map
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]
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)
574
ecasound-iam-c-menu ecasound-iam-mode-map
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)
586
ecasound-iam-cop-menu ecasound-iam-mode-map
587
"Chain Operator menu."
589
["Add..." eci-cop-add (> (length (eci-c-selected)) 0)]
590
["Select..." eci-cop-select t]
591
["Edit..." ecasound-cop-edit t]
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)
598
ecasound-iam-ai-menu ecasound-iam-mode-map
599
"Audio Input Object menu."
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]
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)
612
ecasound-iam-ao-menu ecasound-iam-mode-map
613
"Audio Output Object menu."
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]
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)
628
ecasound-menu global-map
631
["Get session" ecasound t]
633
["Normalize..." ecasound-normalize t]
634
["Signalview..." ecasound-signalview t]
636
["Customize startup..." ecasound-customize-startup t]))
637
(easy-menu-add ecasound-menu global-map)
639
(make-variable-buffer-local
640
(defvar ecasound-mode-string nil))
642
(define-derived-mode ecasound-iam-mode comint-mode "EIAM"
643
"Special mode for ecasound processes in interactive mode.
645
In addition to any hooks its parent mode `comint-mode' might have run,
646
this mode runs the hook `ecasound-iam-mode-hook', as the final step
647
during initialization.
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)))
665
(defun ecasound-mode-line-cop-list (handle)
666
(let ((list (eci-cop-list handle))
667
(sel (1- (eci-cop-selected handle)))
669
(dotimes (i (length list) str)
670
(setq str (format "%s%s%s%s"
672
(if (= i sel) "*" "")
674
(if (= i (length list)) "" ","))))))
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)))
681
(defun ecasound-kill-timer ()
682
"Cancels the background timer.
683
Use this if you want to stop background information fetching."
685
(when ecasound-daemon-timer (cancel-timer ecasound-daemon-timer)))
687
(defun ecasound-kill-daemon ()
688
"Terminate the daemon channel."
690
(ecasound-kill-timer)
691
(when (ecasound-daemon-p)
692
(kill-buffer ecasound-daemon)))
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
703
" [" (ecasound-position-to-string
704
(eci-cs-get-position ecasound-daemon))
705
"/" (ecasound-position-to-string
706
(eci-cs-get-length ecasound-daemon))
711
(eci-cs-selected ecasound-daemon)
712
" [" (if (eci-cs-is-valid-p ecasound-daemon)
715
(mapconcat 'identity (eci-c-list ecasound-daemon) ",")
718
(eci-c-selected ecasound-daemon) ","))))))))
720
(defun ecasound-setup-timer ()
721
(when (and ecasound-timer-flag (ecasound-daemon-p))
722
(setq ecasound-daemon-timer
724
0 ecasound-timer-interval
725
'ecasound-update-mode-line (current-buffer)))))
727
(make-variable-buffer-local
728
(defvar eci-int-output-mode-wellformed-flag nil
729
"Indicates if int-output-mode-wellformed was successfully initialized."))
731
(make-variable-buffer-local
732
(defvar eci-engine-status nil
733
"If non-nil, a string describing the engine-status."))
735
(make-variable-buffer-local
736
(defvar eci-cs-selected nil
737
"If non-nil, a string describing the selected chain setup."))
739
(defcustom ecasound-daemon-host "localhost"
740
"*Host to connect to when attempting to initialize a daemon session.
741
This is typically \"localhost\" when ecasound is invoked in a standard way.
742
However, if you start ecasound trough some script on another host, you might need to adjust
747
(make-variable-buffer-local
748
(defvar ecasound-daemon-port nil
749
"The daemon port number used when starting ecasound."))
752
(defun ecasound (&optional buffer)
753
"Run an inferior ecasound, with I/O through BUFFER.
754
BUFFER defaults to `*ecasound*'.
755
Interactively, a prefix arg means to prompt for BUFFER.
756
If BUFFER exists but ecasound process is not running, make new ecasound
757
process using `ecasound-arguments'.
758
If BUFFER exists and ecasound process is running, just switch to BUFFER.
759
The buffer is put in ecasound mode, giving commands for sending input and
760
completing IAM commands. See `ecasound-iam-mode'.
762
\(Type \\[describe-mode] in the ecasound buffer for a list of commands.)"
765
(and current-prefix-arg
766
(read-buffer "Ecasound buffer: " "*ecasound*"))))
767
(unless buffer (setq buffer "*ecasound*"))
768
(if (not (comint-check-proc buffer))
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)))
793
(setq ecasound-daemon-port (match-string 1 (car elem)))
794
(ecasound-setup-daemon))))
796
(pop-to-buffer buffer)))
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
807
(cons ecasound-daemon-host ecasound-daemon-port)))
809
(setq comint-input-sender 'ecasound-network-send
810
eci-int-output-mode-wellformed-flag t
812
(set (make-variable-buffer-local 'comint-highlight-prompt) nil)
813
(setq comint-output-filter-functions '(ecasound-output-filter))
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")))))
820
(defun ecasound-delete-last-in-and-output ()
821
"Delete the region of text generated by the last in and output.
822
This is usually used to hide ECI requests from the user."
824
(save-excursion (goto-char comint-last-input-end) (forward-line -1)
825
(unless (looking-at ecasound-prompt-regexp)
826
(error "Assumed ecasound-prompt"))
828
comint-last-output-start))
830
(make-variable-buffer-local
831
(defvar eci-last-command nil
832
"Last command sent to the ecasound process."))
834
(make-variable-buffer-local
835
(defvar ecasound-last-parse-start nil
836
"Where to start parsing if output is received.
837
This marker is advanced everytime a successful parse happens."))
839
(defun eci-input-filter (string)
840
"Track commands sent to ecasound.
841
Argument 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))))
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"))
856
(defcustom ecasound-last-command-alist
857
'(("int-output-mode-wellformed" .
858
(setq eci-int-output-mode-wellformed-flag t))
860
(setq ecasound-iam-commands value))
862
(setq eci-map-cop-list (eci-process-map-list value)))
864
(setq eci-map-ladspa-list (eci-process-map-list value)))
866
(setq eci-map-ctrl-list (eci-process-map-list value)))
868
(setq eci-map-preset-list (eci-process-map-list value)))
870
(eci-process-cop-status value))
872
(setq eci-engine-status value))
874
(setq eci-cs-selected value)))
875
"*Alist of command/expression pairs.
876
If `ecasound-last-command' is one of the alist keys, the value of that entry
877
will be evaluated with the variable VALUE bound to the commands
880
:type '(repeat (cons (string :tag "Command") (sexp :tag "Lisp Expression"))))
882
(defcustom ecasound-type-alist
884
("i" . (string-to-number value))
885
("li" . (string-to-number value))
886
("f" . (string-to-number value))
888
("S" . (split-string value ","))
889
("e" . (progn (run-hook-with-args 'ecasound-error-hook value) nil)))
890
"*Alist defining ECI type conversion.
891
Each key is a type, and the values are Lisp expressions. During evaluation
892
the variables TYPE and VALUE are bound respectively."
894
:type '(repeat (cons (string :tag "Type") (sexp :tag "Lisp Expression"))))
896
(make-variable-buffer-local
897
(defvar eci-return-type nil
898
"The return type of the last received return value as a string."))
900
(make-variable-buffer-local
901
(defvar eci-return-value nil
902
"The last received return value as a string."))
904
(make-variable-buffer-local
905
(defvar eci-result nil
906
"The last received return value as a Lisp Object."))
908
(defun ecasound-process-result (type value)
909
"Process ecasound ECI result.
910
This function is called if `ecasound-output-filter' detected an ECI reply.
911
Argument TYPE is the ECI type as a string and argument VALUE is the value as
913
This function uses `ecasound-type-alist' and `ecasound-last-command-alist'
914
to 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)))
919
(setq value (eval (cdar tcode)))
920
(error "Return type '%s' not defined in `ecasound-type-alist'" type))
921
(setq eci-return-value value
923
eci-result (if lcode (eval (cdar lcode)) value))))
925
(defun ecasound-output-filter (string)
926
"Parse ecasound process output.
927
This function should be used on `comint-output-filter-functions' hook.
928
STRING 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)))))
933
(let (type value (end (copy-marker end)))
935
(while (re-search-forward
936
"\\([0-9]\\{1,3\\}\\) \\([0-9]\\{1,5\\}\\)\\( \\(.*\\)\\)?\n"
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
944
(if (> (- (point-max) (point)) msgsize)
946
(forward-char msgsize)
947
(if (not (save-match-data
949
"\\(\n\n\\|\r\n\r\n\\)")))
950
(error "Malformed ECI message")
953
(when (= msgsize (length msg))
954
(if (and (= loglevel 256)
955
(string= return-type "e"))
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)
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)
968
type (if (string-match "\\(.*\\)\r" return-type)
969
(match-string 1 return-type)
972
(ecasound-process-result type value)))))))
974
(defmacro defeci (name &optional args doc &rest body)
975
"Defines an ECI command.
976
Argument NAME is used for the function name with eci- as prefix.
977
Optional argument ARGS specifies the arguments this ECI command has.
978
Optional argument DOC is the docstring used for the defined function.
979
BODY can start with keyword arguments to indicated certain special cases. The
980
following keyword arguments are implemented:
981
:cache VARNAME The command should try to find a cached version of the result
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
988
(let ((sym (intern (format "eci-%S" name)))
989
(pcmpl-sym (intern (format "pcomplete/ecasound-iam-mode/%S" name)))
992
`(format ,(format "%S %s"
993
name (mapconcat #'caddr args ","))
996
`(if (or (stringp ,(car arg))
997
(numberp ,(car arg)))
999
(mapconcat #'identity ,(car arg) ",")))
1003
cache cache-doc pcmpl aliases)
1004
(while (keywordp (car 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)))
1011
(when (and (not (eq aliases nil))
1012
(not (consp aliases)))
1013
(setq aliases (list aliases)))
1016
`(make-variable-buffer-local
1017
(defvar ,cache ,@(if cache-doc (list nil cache-doc) (list nil)))))
1019
,(if args (append (mapcar #'car args) `(&optional buffer-or-process))
1020
`(&optional buffer-or-process))
1022
,(if args `(interactive
1024
(mapcar (lambda (x) (when x (setq done t)))
1025
(mapcar #'stringp (mapcar #'cadr args)))
1027
(mapconcat #'identity (mapcar #'cadr args) "\n")
1028
`(list ,@(mapcar #'cadr args))))
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
1045
(lambda (alias) `(defalias ',(intern (format "eci-%S" alias))
1049
,(if (and (eq pcmpl 'doc) (stringp doc) (not (string= doc "")))
1050
`(defun ,pcmpl-sym ()
1052
(throw 'pcompleted t))
1053
`(defun ,pcmpl-sym ()
1057
`(defalias ',(intern (format "pcomplete/ecasound-iam-mode/%S" alias))
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.
1066
((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1068
Use `eci-map-cop-list' to fill this variable with data.")
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.
1075
((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1077
Use `eci-map-ctrl-list' to fill this list with data.")
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.
1084
((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1086
Use `eci-map-ladspa-list' to fill this list with data.")
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.
1093
((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
1095
Use `eci-map-preset-list' to fill this list with data.")
1097
;;; Ecasound-iam-mode pcomplete functions
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)
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)
1108
(pcomplete-arg 'first)))
1109
(set (make-local-variable 'pcomplete-parse-arguments-function)
1110
'ecasound-iam-pcomplete-parse-arguments))
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)))
1120
(while (< (point) end)
1121
(skip-chars-forward " \t\n,:")
1122
(setq begins (cons (point) begins))
1125
(skip-chars-forward "^ \t\n,:")
1126
(if (eq (char-before) ?\\)
1127
(skip-chars-forward " \t\n,:")
1129
(setq args (cons (buffer-substring-no-properties
1130
(car begins) (point))
1132
(cons (reverse args) (reverse begins)))))
1134
(defun ecasound-input-file-or-device ()
1135
"Return a list of possible completions for input device name."
1141
(concat "^" (regexp-quote pcomplete-stub)) elt)
1143
(list "alsa" "alsahw" "alsalb" "alsaplugin"
1144
"arts" "loop" "null" "stdin")))
1145
(pcomplete-entries)))
1149
(defun eci-map-find-args (arg map)
1150
"Return the argument specification for ARG in MAP."
1153
(if (string= (nth 1 (car map)) arg)
1154
(setq result (nthcdr 3 (car map))
1156
(setq map (cdr map))))
1159
(defun ecasound-echo-arg (arg)
1160
"Display a chain operator parameter description from a eci-map-*-list
1163
(let ((type (nth 5 arg)))
1164
(message "%s%s%s, default %S%s%s"
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))
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")))
1177
;;; ECI --- The Ecasound Control Interface
1180
"Ecasound Control Interface."
1183
(defcustom eci-program (or (getenv "ECASOUND") "ecasound")
1184
"*Program to invoke when doing `eci-init'."
1186
:type '(choice string (cons string string)))
1188
(defcustom eci-arguments '("-c" "-D" "-d:256")
1189
"*Arguments used by `eci-init'."
1191
:type 'ecasound-args)
1193
(defvar eci-hide-output nil
1194
"If non-nil, `eci-command' will remove the output generated.")
1196
(defmacro eci-hide-output (&rest eci-call)
1197
"Hide the output of this ECI-call.
1198
If a daemon-channel is active, use that, otherwise set `eci-hide-output' to t.
1199
Argument ECI-CALL is a symbol followed by its aruments if any."
1200
`(if (ecasound-daemon-p)
1201
,(append eci-call (list 'ecasound-daemon))
1202
(let ((eci-hide-output t))
1206
"Initialize a programmatic ECI session.
1207
Every call to this function results in a new sub-process being created
1208
according to `eci-program' and `eci-arguments'. Returns the newly
1210
The caller is responsible for terminating the subprocess at some point."
1219
(while (accept-process-output (get-buffer-process (current-buffer)) 1))
1220
(if (eci-command "int-output-mode-wellformed")
1223
(defun eci-interactive-startup ()
1224
"Used to interactively startup a ECI session using `eci-init'.
1225
This will mostly be used for testing sessions and is equivalent
1228
(switch-to-buffer (eci-init)))
1230
(defun ecasound-find-buffer (buffer-or-process)
1232
((bufferp buffer-or-process)
1234
((processp buffer-or-process)
1235
(process-buffer buffer-or-process))
1236
((and (eq major-mode 'ecasound-iam-mode)
1237
(comint-check-proc (current-buffer)))
1239
(t (error "Could not determine suitable ecasound buffer"))))
1241
(defun ecasound-find-parent (buffer-or-process)
1242
(with-current-buffer (ecasound-find-buffer buffer-or-process)
1247
(defun eci-command (command &optional buffer-or-process)
1248
"Send a ECI command to a ECI host process.
1249
COMMAND is the string to be sent, without a newline character.
1250
If BUFFER-OR-PROCESS is nil, first look for a ecasound process in the current
1251
buffer, then for a ecasound buffer with the name *ecasound*,
1252
otherwise use the buffer or process supplied.
1253
Return the string we received in reply to the command except
1254
`eci-int-output-mode-wellformed-flag' is set, which means we can parse the
1255
output via `eci-parse' and return a meaningful value."
1256
(interactive "sECI Command: ")
1257
(let* ((buf (ecasound-find-buffer buffer-or-process))
1258
(proc (get-buffer-process buf))
1259
(ecasound-sending-command t))
1260
(with-current-buffer buf
1261
(let ((moving (= (point) (point-max))))
1262
(setq eci-result 'waiting)
1263
(goto-char (process-mark proc))
1265
(let (comint-eol-on-send)
1266
(comint-send-input))
1267
(let ((here (point)) result)
1268
(while (eq eci-result 'waiting)
1269
(accept-process-output proc 1))
1271
(if eci-int-output-mode-wellformed-flag
1273
;; Backward compatibility. Just return the string
1274
(buffer-substring-no-properties here (save-excursion
1275
; Strange hack to avoid fields
1278
(if (not (= here (point)))
1281
(if moving (goto-char (point-max)))
1282
(when (and eci-hide-output result)
1283
(ecasound-delete-last-in-and-output))
1286
(defsubst eci-error-p ()
1287
"Predicate which can be used to check if the last command produced an error."
1288
(string= eci-return-type "e"))
1290
;;; ECI commands implemented as lisp functions
1292
(defeci int-cmd-list ()
1294
:cache ecasound-iam-commands
1295
:cache-doc "Available Ecasound IAM commands.")
1301
(defeci cs-add ((chainsetup "sChainsetup to add: " "%s"))
1302
"Adds a new chainsetup with name `name`."
1305
(defeci cs-connect ()
1306
"Connect currently selected chainsetup to engine."
1309
(defeci cs-connected ()
1310
"Returns the name of currently connected chainsetup."
1313
(defeci cs-disconnect ()
1314
"Disconnect currently connected chainsetup."
1319
(if current-prefix-arg
1320
(prefix-numeric-value current-prefix-arg)
1321
(read-minibuffer (format "Time in seconds to forward %s: "
1322
(eci-hide-output eci-cs-selected)))) "%f")))
1324
(defeci cs-get-length ()
1328
(defeci cs-get-length-samples ()
1330
:alias get-length-samples)
1332
(defeci cs-get-position ()
1334
:alias (cs-getpos getpos get-position))
1336
(defeci cs-index-select ((index "nChainsetup index: " "%d"))
1337
"Selects a chainsetup based on a short index.
1338
Chainsetup names can be rather long. This command can be used to avoid
1339
typing these long names. INDEX is an integer value, where 1 refers to the
1340
first audio input/output. You can use `eci-cs-list' and `eci-cs-status' to get
1341
a full list of currently available chainsetups."
1344
(defeci cs-is-valid ()
1345
"Whether currently selected chainsetup is valid (=can be connected)?"
1347
(let ((val (eci-command "cs-is-valid" buffer-or-process)))
1349
(message "Chainsetup is%s valid" (if (= val 0) "" " not")))
1352
(defun eci-cs-is-valid-p (&optional buffer-or-process)
1353
"Predicate function used to determine chain setup validity."
1354
(case (eci-cs-is-valid buffer-or-process)
1357
(otherwise (error "Unexcpected return value from cs-is-valid"))))
1360
"Returns a list of all chainsetups."
1362
(let ((val (eci-command "cs-list" buffer-or-process)))
1364
(message (concat "Available chainsetups: "
1365
(mapconcat #'identity val ", "))))
1368
(defeci cs-load ((filename "fChainsetup filename: " "%s"))
1369
"Adds a new chainsetup by loading it from file FILENAME.
1370
FILENAME is then the selected chainsetup."
1371
:pcomplete (pcomplete-here (pcomplete-entries)))
1373
(defeci cs-remove ()
1374
"Removes currently selected chainsetup."
1379
(if current-prefix-arg
1380
(prefix-numeric-value current-prefix-arg)
1381
(read-minibuffer "Time in seconds to rewind chainsetup: ")) "%f"))
1382
"Rewinds the current chainsetup position by `time-in-seconds` seconds."
1388
(defeci cs-save-as ((filename "FChainsetup filename: " "%s"))
1389
"Saves currently selected chainsetup to file FILENAME."
1390
:pcomplete (pcomplete-here (pcomplete-entries)))
1392
(defeci cs-selected ()
1393
"Returns the name of currently selected chainsetup."
1395
(let ((val (with-current-buffer (ecasound-find-parent buffer-or-process)
1396
(setq eci-cs-selected (eci-command "cs-selected"
1397
buffer-or-process)))))
1399
(message "Selected chainsetup: %s" val))
1402
(defeci cs-set-position
1404
(if current-prefix-arg
1405
(prefix-numeric-value current-prefix-arg)
1406
(read-minibuffer "Position: ")) "%f"))
1407
"Sets the chainsetup position to POS seconds from the beginning.
1408
Position of all inputs and outputs attached to the selected chainsetup is also
1410
:alias (cs-setpos setpos set-position))
1414
(defeci c-add ((chains "sChain(s) to add: " "%s"))
1415
"Adds a set of chains. Added chains are automatically selected.
1416
If argument CHAINS is a list, its elements are concatenated with ','.")
1419
"Clear selected chains by removing all chain operators and controllers.
1420
Doesn't change how chains are connected to inputs and outputs."
1423
(defun ecasound-read-list (prompt list)
1424
"Interactively prompt for a number of inputs until empty string.
1425
PROMPT is used as prompt and LIST is a list of choices to choose from."
1432
(setq current (completing-read prompt (mapcar #'list avail)))
1434
(setq result (cons current result)
1435
avail (delete current avail)))
1439
((chains (ecasound-read-list "Chain to deselect: " (eci-c-selected)) "%s"))
1441
:pcomplete (while (pcomplete-here (eci-c-selected))))
1444
"Returns a list of all chains.")
1447
"Toggle chain muting. When chain is muted, all data that goes
1451
(defeci c-select ((chains (ecasound-read-list "Chain: " (eci-c-list)) "%s"))
1452
"Selects chains. Other chains are automatically deselected."
1455
(defeci c-selected ()
1457
(let ((val (eci-command "c-selected" buffer-or-process)))
1460
(message "No selected chains")
1461
(message (concat "Selected chains: "
1462
(mapconcat #'identity val ", ")))))
1465
(defeci c-select-all ()
1466
"Selects all chains."
1471
(completing-read "Chainsetup: " (mapcar #'list (eci-cs-list)))
1474
:pcomplete (pcomplete-here (eci-hide-output eci-cs-list)))
1478
(let ((file (read-file-name "Input filename: ")))
1479
(if (file-exists-p file)
1480
(expand-file-name file)
1483
"Adds a new input object."
1484
:pcomplete (pcomplete-here (ecasound-input-file-or-device)))
1486
(defeci ai-attach ()
1487
"Attaches the currently selected audio input object to all selected chains."
1492
(if current-prefix-arg
1493
(prefix-numeric-value current-prefix-arg)
1494
(read-minibuffer (format "Time in seconds to forward %s: "
1495
(eci-hide-output eci-ai-selected)))) "%f"))
1496
"Selected audio input object is forwarded by SECONDS.
1497
Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
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 input object is rewinded by SECONDS.
1508
Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1512
(defeci ai-index-select ((index "nAudio Input index: " "%d"))
1513
"Select some audio input object based on a short index.
1514
Especially file names can be rather long. This command can be used to avoid
1515
typing these long names when selecting audio objects.
1516
INDEX is an integer value, where 1 refers to the first audio input.
1517
You can use `eci-ai-list' to get a full list of currently available inputs."
1523
(defeci ai-remove ()
1524
"Removes the currently selected audio input object from the chainsetup."
1526
(defeci ao-remove ()
1527
"Removes the currently selected audio output object from the chainsetup."
1530
(defeci ai-select ((name "sAudio Input Object name: " "%s"))
1531
"Selects an audio object.
1532
NAME refers to the string used when creating the object. Note! All input
1533
object names are required to be unique. Similarly all output names need to be
1534
unique. However, it's possible that the same object name exists both as an
1535
input and as an output."
1536
:pcomplete (pcomplete-here (eci-hide-output eci-ai-list)))
1538
(defeci ai-selected ()
1539
"Returns the name of the currently selected audio input object."
1542
(defeci ao-add ((filename "FOutput filename: " "%s"))
1544
:pcomplete (pcomplete-here (ecasound-input-file-or-device)))
1546
(defeci ao-add-default)
1548
(defeci ao-attach ()
1549
"Attaches the currently selected audio output object to all selected chains."
1554
(if current-prefix-arg
1555
(prefix-numeric-value current-prefix-arg)
1556
(read-minibuffer (format "Time in seconds to forward %s: "
1557
(eci-hide-output eci-ao-selected)))) "%f"))
1558
"Selected audio output object is forwarded by SECONDS.
1559
Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1563
(defeci ao-index-select ((index "nAudio Output index: " "%d"))
1564
"Select some audio output object based on a short index.
1565
Especially file names can be rather long. This command can be used to avoid
1566
typing these long names when selecting audio objects.
1567
INDEX is an integer value, where 1 refers to the first audio output.
1568
You can use `eci-ao-list' to get a full list of currently available outputs."
1576
(if current-prefix-arg
1577
(prefix-numeric-value current-prefix-arg)
1578
(read-minibuffer (format "Time in seconds to rewind %s: "
1579
(eci-hide-output eci-ai-selected)))) "%f"))
1580
"Selected audio output object is rewinded by SECONDS.
1581
Time should be given as a floating point value (eg. 0.001 is the same as 1ms)."
1585
(defeci ao-select ((name "sAudio Output Object name: " "%s"))
1586
"Selects an audio object.
1587
NAME refers to the string used when creating the object. Note! All output
1588
object names need to be unique. However, it's possible that the same object
1589
name exists both as an input and as an output."
1590
:pcomplete (pcomplete-here (eci-hide-output eci-ao-list)))
1592
(defeci ao-selected ()
1593
"Returns the name of the currently selected audio output object."
1596
(defeci engine-status ()
1597
"Returns a string describing the engine status
1598
\(running, stopped, finished, error, not ready)."
1600
(with-current-buffer (ecasound-find-parent buffer-or-process)
1601
(setq eci-engine-status (eci-command "engine-status" buffer-or-process))))
1603
(defmacro ecasound-complete-cop-map (map)
1604
(let ((m (intern (format "eci-map-%S-list" map))))
1607
((= pcomplete-last 2)
1608
(pcomplete-next-arg)
1610
(sort (mapcar (lambda (elt) (nth 1 elt))
1611
(eci-hide-output ,m))
1613
((> pcomplete-last 2)
1615
(nth (- pcomplete-last 3)
1617
(pcomplete-arg -1) (eci-hide-output ,m)))))))))
1621
(if current-prefix-arg
1622
(read-string "Chainop to add: " "-")
1626
(append (eci-hide-output eci-map-cop-list)
1627
(eci-hide-output eci-map-ladspa-list)
1628
(eci-hide-output eci-map-preset-list))))
1629
(entry (or (assoc cop (eci-map-cop-list))
1630
(assoc cop (eci-map-ladspa-list))
1631
(assoc cop (eci-map-preset-list))))
1632
(arg (nth 1 entry)))
1635
((assoc cop (eci-map-cop-list))
1636
(concat "-" arg ":"))
1637
((assoc cop (eci-map-ladspa-list))
1638
(concat "-el:" arg ","))
1639
((assoc cop (eci-map-preset-list))
1640
(concat "-pn:" arg ",")))
1641
(mapconcat #'ecasound-read-copp (nthcdr 3 entry) ","))))
1647
((= pcomplete-last 1)
1653
(concat "-" (nth 1 elt) ":"))
1654
(eci-hide-output eci-map-cop-list)))))
1655
((string= (pcomplete-arg) "-el")
1656
(ecasound-complete-cop-map ladspa))
1657
((string= (pcomplete-arg) "-pn")
1658
(ecasound-complete-cop-map preset))
1659
((> pcomplete-last 1)
1661
(nth (- pcomplete-last 2)
1663
(substring (pcomplete-arg) 1)
1664
(eci-hide-output eci-map-cop-list))))))
1665
(throw 'pcompleted t)))
1672
((index "nChainop to select: " "%d")))
1674
(defeci cop-selected)
1676
;; FIXME: Command seems to be broken in CVS.
1677
(defeci cop-set ((cop "nChainop id: " "%d")
1678
(copp "nParameter id: " "%d")
1679
(value "nValue: " "%f"))
1680
"Changes the value of a single chain operator parameter.
1681
Unlike other chain operator commands, this can also be used during processing."
1686
(if current-prefix-arg
1687
(read-string "Controller to add: " "-")
1690
"Chain operator controller controller: "
1691
(eci-hide-output eci-map-ctrl-list))
1692
(eci-hide-output eci-map-ctrl-list))))
1693
(concat "-" (nth 1 ctrl) ":"
1694
(mapconcat #'ecasound-read-copp (nthcdr 3 ctrl) ","))))
1698
((index "nController to select: " "%d")))
1701
((index "nChainop parameter to select: " "%d")))
1706
((value "nValue for Chain operator parameter: " "%f")))
1710
(defun eci-example ()
1711
"Implements the example given in the ECI documentation."
1713
(save-current-buffer
1714
(set-buffer (eci-init))
1715
(display-buffer (current-buffer))
1716
(eci-cs-add "play_chainsetup")
1717
(eci-c-add "1st_chain")
1718
(call-interactively #'eci-ai-add)
1719
(eci-ao-add "/dev/dsp")
1720
(eci-cop-add "-efl:100")
1721
(eci-cop-select 1) (eci-copp-select 1)
1723
(eci-command "start")
1725
(while (and (string= (eci-engine-status) "running")
1726
(< (eci-get-position) 15))
1727
(eci-copp-set (+ (eci-copp-get) 500))
1729
(eci-command "stop")
1731
(message (concat "Chain operator status: "
1732
(eci-command "cop-status")))))
1734
(defun eci-make-temp-file-name (suffix)
1735
(concat (make-temp-name
1736
(expand-file-name "emacs-eci" temporary-file-directory))
1739
(defun ecasound-read-from-minibuffer (prompt default)
1740
(let ((result (read-from-minibuffer
1741
(format "%s (default %S): " prompt default)
1742
nil nil nil nil default)))
1743
(if (and result (not (string= result "")))
1749
(make-variable-buffer-local
1750
(defvar ecasound-markers nil
1751
"Alist of currently defined markers for this session.
1752
Key is a chainsetup name, and Value is another alist of name/position pairs."))
1754
(defun ecasound-set-mark (name &optional chainsetup pos)
1755
"Set NAME as a marker for the currently selected chainsetup."
1756
(interactive (list (read-string "Marker name: ")
1757
(eci-cs-selected) (eci-cs-get-position)))
1758
(unless chainsetup (eci-cs-selected))
1759
(unless pos (setq pos (eci-cs-get-position)))
1760
(let ((e (assoc chainsetup ecasound-markers)))
1762
(setq ecasound-markers (cons (cons chainsetup (list (cons name pos)))
1764
(if (assoc name (cdr e))
1765
(setcdr (assoc name (cdr e)) pos)
1766
(setcdr e (cons (cons name pos) (cdr e)))))
1767
(if (interactive-p) (message "Mark %s set at position %f" name pos) pos)))
1769
(defun ecasound-goto-mark (name)
1770
"Set the position previously recorded as NAME."
1773
(completing-read "Goto mark: "
1774
(cdr (assoc (eci-cs-selected) ecasound-markers)))))
1775
(let* ((cs (eci-cs-selected))
1776
(e (assoc cs ecasound-markers)))
1778
(message "No marks set for chainsetup %s" cs)
1779
(let ((mark (assoc name (cdr e))))
1781
(message "Mark %s is not set for chainsetup %s" name cs)
1782
(eci-cs-set-position (cdr mark)))))))
1784
;;; Ecasound Signalview
1786
(defconst ecasound-signalview-clipped-threshold (- 1.0 (/ 1.0 16384)))
1788
(defconst ecasound-signalview-bar-length 55)
1790
(defun ecasound-position-to-string (secs &optional long)
1791
"Convert a floating point position value in SECS to a string.
1792
If optional argument LONG is non-nil, produce a full 00:00.00 string,
1793
otherwise ignore zeors as well as colons and dots on the left side."
1794
(let ((str (format "%02d:%02d.%02d"
1796
(% (round (floor secs)) 60)
1797
(* (- secs (floor secs)) 100))))
1800
(let ((idx 0) (len (1- (length str))))
1801
(while (and (< idx len)
1802
(let ((ch (aref str idx)))
1803
(or (eq ch ?0) (eq ch ?:) (eq ch ?.))))
1805
(substring str idx)))))
1807
(defun ecasound-signalview (bufsize format input output)
1808
"Interactively view the singal of a audio stream.
1809
After invokation, this function displays the signal level of the individual
1810
channels in INPUT based on the information given in FORMAT."
1813
(ecasound-read-from-minibuffer "Buffersize" "128")
1814
(ecasound-read-from-minibuffer "Format" "s16_le,2,44100,i")
1815
(let ((file (read-file-name "Input: ")))
1816
(if (file-exists-p file)
1817
(expand-file-name file)
1819
(ecasound-read-from-minibuffer "Output" "null")))
1820
(let* (;; THis saves time
1821
(ecasound-parse-cleanup-buffer nil)
1823
(channels (string-to-number (nth 1 (split-string format ","))))
1824
(chinfo (make-vector channels nil)))
1825
(dotimes (ch channels) (aset chinfo ch (cons 0 0)))
1826
(eci-cs-add "signalview" handle)
1827
(eci-c-add "analysis" handle)
1828
(eci-cs-set-audio-format format handle)
1829
(eci-ai-add input handle)
1830
(eci-ao-add output handle)
1831
(eci-cop-add "-evp" handle)
1832
(eci-cop-add "-ev" handle)
1833
(set-buffer (get-buffer-create "*Ecasound-signalview*"))
1835
(dotimes (ch channels)
1837
(setq header-line-format
1838
(list (concat "Channel#"
1839
(make-string (- ecasound-signalview-bar-length 3) 32)
1840
"| max-value clipped")))
1841
(set (make-variable-buffer-local 'ecasignalview-position) "unknown")
1842
(set (make-variable-buffer-local 'ecasignalview-engine-status) "unknown")
1843
(setq mode-line-format
1846
(- ecasound-signalview-bar-length 3)
1847
(format "Input: %s, output: %s" input output)
1848
'ecasignalview-engine-status)
1849
" | " 'ecasignalview-position))
1850
(switch-to-buffer-other-window (current-buffer))
1851
(eci-cs-connect handle)
1854
(eci-cop-select 1 handle)
1855
(while (string= (setq ecasignalview-engine-status
1856
(eci-engine-status handle)) "running")
1857
(let ((inhibit-quit t) (inhibit-redisplay t))
1858
(setq ecasignalview-position
1859
(ecasound-position-to-string (eci-cs-get-position handle) t))
1860
(delete-region (point-min) (point-max))
1861
(dotimes (ch channels)
1862
(insert (format "ch%d: " (1+ ch)))
1863
(let ((val (progn (eci-copp-select (1+ ch) handle)
1864
(eci-copp-get handle)))
1865
(bl ecasound-signalview-bar-length))
1868
(make-string (round (* val bl)) ?*)
1869
(make-string (- bl (round (* val bl))) ? )))
1870
(if (> val (car (aref chinfo ch)))
1871
(setcar (aref chinfo ch) val))
1872
(if (> val ecasound-signalview-clipped-threshold)
1873
(incf (cdr (aref chinfo ch))))
1874
(insert (format "| %.4f %d\n" (car (aref chinfo ch))
1875
(cdr (aref chinfo ch))))))
1876
(goto-char (point-min)))
1878
(fit-window-to-buffer))
1879
(goto-char (point-max))
1880
(let ((pos (point)))
1885
(assoc "Volume analysis"
1887
(eci-cop-status handle)))))))
1890
(fit-window-to-buffer)))
1892
(defun ecasound-normalize (filename)
1893
"Normalize a audio file using ECI."
1894
(interactive "fFile to normalize: ")
1895
(let ((tmpfile (eci-make-temp-file-name ".wav")))
1897
(with-current-buffer (eci-init)
1898
(display-buffer (current-buffer)) (sit-for 1)
1899
(eci-cs-add "analyze") (eci-c-add "1")
1900
(eci-ai-add filename) (eci-ao-add tmpfile)
1902
(message "Analyzing sample data...")
1903
(eci-cs-connect) (eci-run)
1904
(eci-cop-select 1) (eci-copp-select 2)
1905
(let ((gainfactor (eci-copp-get)))
1907
(if (<= gainfactor 1)
1908
(message "File already normalized!")
1909
(eci-cs-add "apply") (eci-c-add "1")
1910
(eci-ai-add tmpfile) (eci-ao-add filename)
1911
(eci-cop-add "-ea:100")
1914
(eci-copp-set (* gainfactor 100))
1915
(eci-cs-connect) (eci-run) (eci-cs-disconnect)
1917
(if (file-exists-p tmpfile)
1918
(delete-file tmpfile)))))
1920
;;; Utility functions for converting strings to data-structures.
1922
(defvar eci-cop-status-header
1923
"### Chain operator status (chainsetup '\\([^']+\\)') ###\n")
1925
(defun eci-process-cop-status (string)
1927
(insert string) (goto-char (point-min))
1928
(when (re-search-forward eci-cop-status-header nil t)
1930
(while (re-search-forward "Chain \"\\([^\"]+\\)\":\n" nil t)
1931
(let ((c (match-string-no-properties 1)) chain)
1932
(while (re-search-forward
1933
"\t\\([0-9]+\\)\\. \\(.+\\): \\(.*\\)\n?" nil t)
1934
(let ((n (string-to-number (match-string 1)))
1935
(name (match-string-no-properties 2))
1940
"\\[\\([0-9]+\\)\\] \\(.*\\) \\([0-9.-]+\\)$"
1942
(list (match-string-no-properties 2 elt)
1943
(string-to-number (match-string 1 elt))
1944
(string-to-number (match-string 3 elt)))))
1946
(match-string-no-properties 3) ", "))))
1947
(if (looking-at "\tStatus info:\n")
1955
(progn (forward-line 1) (point))
1956
(or (re-search-forward "\n\n" nil t)
1958
(setq chain (cons (append (list name n) args) chain))))
1959
(setq result (cons (reverse (append chain (list c))) result))))
1962
(defun eci-process-map-list (string)
1963
"Parse the output of a map-xxx-list ECI command and return an alist.
1964
STRING is the string returned by a map-xxx-list command."
1968
(list (nth 1 elt) (nth 0 elt) (nth 2 elt))
1969
(let (res (count (string-to-number (nth 3 elt))))
1970
(setq elt (nthcdr 4 elt))
1975
(list (nth 0 elt) (nth 1 elt)
1976
(string-to-number (nth 2 elt)) ;; default value
1977
(when (string= (nth 3 elt) "1")
1978
(string-to-number (nth 4 elt)))
1979
(when (string= (nth 5 elt) "1")
1980
(string-to-number (nth 6 elt)))
1982
((string= (nth 7 elt) "1")
1984
((string= (nth 8 elt) "1")
1986
((string= (nth 9 elt) "1")
1988
((string= (nth 10 elt) "1")
1993
(mapcar (lambda (str) (split-string str ","))
1994
(split-string string "\n"))))
1996
(defeci cs-set-audio-format
1997
((format (ecasound-read-from-minibuffer
1998
"Audio format" "s16_le,2,44100,i") "%s"))
1999
"Set the default sample parameters for currently selected chainsetup.
2000
For example cd-quality audio would be \"16,2,44100\"."
2003
(defeci cop-register)
2004
(defeci preset-register)
2005
(defeci ctrl-register)
2009
(defeci ladspa-register)
2011
(defun ecasound-read-copp (copp)
2012
"Interactively read one chainop parameter."
2013
(let* ((completion-ignore-case t)
2014
(default (format "%S" (nth 2 copp)))
2016
(read-from-minibuffer
2019
" (default " default "): ")
2022
(if (and answer (not (string= answer "")))
2028
(defvar ecasound-cop-edit-mode-map
2029
(let ((map (make-keymap)))
2030
(set-keymap-parent map widget-keymap)
2033
(define-derived-mode ecasound-cop-edit-mode fundamental-mode "COP-edit"
2034
"A major mode for editing ecasound chain operators.")
2036
(defun ecasound-cop-edit ()
2037
"Edit the chain operator settings of the current session interactively.
2038
This is done using the ecasound-cop widget."
2040
(let ((cb (current-buffer))
2041
(chains (eci-cop-status)))
2042
(switch-to-buffer-other-window (generate-new-buffer "*cop-edit*"))
2043
(ecasound-cop-edit-mode)
2046
(widget-insert (format "Chain %s:\n" (car chain)))
2049
(apply 'widget-create 'ecasound-cop :buffer cb cop))
2053
(goto-char (point-min))))
2055
(define-widget 'ecasound-cop 'default
2057
:children is a list of ecasound-copp widgets."
2060
(let ((args (widget-get widget :args)))
2062
(widget-put widget :tag (car args))
2063
(widget-put widget :cop-number (nth 1 args))
2064
(widget-put widget :args (cddr args))))
2072
(apply 'widget-create-child-and-convert
2073
widget '(ecasound-copp) copp-arg))
2074
(widget-get widget :args))))
2076
(lambda (widget escape)
2081
(widget-create-child-value
2082
widget '(ecasound-cop-select) (widget-get widget :cop-number))))))
2083
:format "%i %t\n%v")
2085
(define-widget 'ecasound-cop-select 'link
2086
"Select this chain operator parameter."
2087
:help-echo "RET to select."
2092
(lambda (widget &rest ignore)
2093
(let ((buffer (widget-get (widget-get widget :parent) :buffer)))
2094
(eci-cop-select (widget-value widget) buffer))))
2096
;;;; A Chain Operator Parameter Widget.
2098
; This is used as a component of the cop widget.
2100
(define-widget 'ecasound-copp 'number
2101
"A Chain operator parameter."
2102
:action 'ecasound-copp-action
2103
:convert-widget 'ecasound-copp-convert
2104
:format " %i %v (%t)\n"
2105
:format-handler 'ecasound-copp-format-handler
2108
(defun ecasound-copp-convert (widget)
2110
(let ((args (widget-get widget :args)))
2112
(widget-put widget :tag (car args))
2113
(widget-put widget :copp-number (nth 1 args))
2114
(widget-put widget :value (nth 2 args))
2115
(widget-put widget :args nil)))
2118
(defun ecasound-copp-format-handler (widget escape)
2124
(widget-create-child-value
2126
'(ecasound-copp-select)
2127
(widget-get widget :copp-number))))
2132
(widget-create-child-value
2135
(string-to-number (widget-get widget :value)))))))
2137
(defun ecasound-copp-action (widget &rest ignore)
2138
"Sets WIDGETs value in its associated ecasound buffer."
2139
(let ((buffer (widget-get (widget-get widget :parent) :buffer)))
2140
(if (widget-apply widget :match (widget-value widget))
2142
(eci-cop-set (widget-get (widget-get widget :parent) :cop-number)
2143
(widget-get widget :copp-number)
2144
(widget-value widget)
2146
(message "Invalid"))))
2148
(defvar ecasound-copp-select-keymap
2149
(let ((map (copy-keymap widget-keymap)))
2150
(define-key map "+" 'ecasound-copp-increase)
2151
(define-key map "-" 'ecasound-copp-decrease)
2153
"Keymap used inside an copp.")
2155
(defun ecasound-copp-increase (pos &optional event)
2157
;; BUG, if we do this, the field is suddently no longer editable, why???
2158
(let ((widget (widget-get (widget-at pos) :parent)))
2161
(+ (widget-value widget) 1))
2162
(widget-apply widget :action)
2165
(defun ecasound-copp-decrease (pos &optional event)
2167
(let ((widget (widget-get (widget-at pos) :parent)))
2170
(- (widget-value widget) 1))
2171
(widget-apply widget :action)
2174
(define-widget 'ecasound-copp-select 'link
2175
"Select this chain operator parameter."
2176
:help-echo "RET to select, +/- to set in steps."
2177
:keymap ecasound-copp-select-keymap
2179
:action 'ecasound-copp-select-action)
2181
(defun ecasound-copp-select-action (widget &rest ignore)
2182
"Selects WIDGET in its associated ecasound buffer."
2183
(let ((buffer (widget-get (widget-get (widget-get widget :parent) :parent)
2185
(eci-copp-select (widget-get widget :value) buffer)))
2187
(define-widget 'slider 'default
2189
:action 'widget-slider-action
2194
(let ((map (copy-keymap widget-keymap)))
2195
(define-key map "\C-m" 'widget-slider-press)
2196
(define-key map "+" 'widget-slider-increase)
2197
(define-key map "-" 'widget-slider-decrease)
2199
:value-create 'widget-slider-value-create
2200
:value-delete 'ignore
2201
:value-get 'widget-value-value-get
2205
(defun widget-slider-press (pos &optional event)
2206
"Invoke slider at POS."
2208
(let ((button (get-char-property pos 'button)))
2210
(widget-apply-action
2213
(- pos (overlay-start (widget-get button :button-overlay))))
2215
(let ((command (lookup-key widget-global-map (this-command-keys))))
2216
(when (commandp command)
2217
(call-interactively command))))))
2219
(defun widget-slider-increase (pos &optional event)
2220
"Increase slider at POS."
2222
(widget-slider-change pos #'+ 1 event))
2224
(defun widget-slider-decrease (pos &optional event)
2225
"Decrease slider at POS."
2227
(widget-slider-change pos #'- 1 event))
2229
(defun widget-slider-change (pos function value &optional event)
2230
"Change slider at POS by applying FUNCTION to old-value and VALUE."
2231
(let ((button (get-char-property pos 'button)))
2233
(widget-apply-action
2234
(widget-value-set button (apply function (widget-value button) value))
2236
(let ((command (lookup-key widget-global-map (this-command-keys))))
2237
(when (commandp command)
2238
(call-interactively command))))))
2240
(defun widget-slider-action (widget &rest ignore)
2241
"Set the current :parent value to :value."
2242
(widget-value-set (widget-get widget :parent)
2243
(widget-value widget)))
2245
(defun widget-slider-value-create (widget)
2246
"Create a sliders value."
2247
(let ((size (widget-get widget :size))
2248
(value (string-to-int (format "%.0f" (widget-get widget :value))))
2250
(insert-char ?\ value)
2252
(insert-char ?\ (- size value 1))))
2255
;;; Ecasound .ewf major mode
2257
(defgroup ecasound-ewf nil
2258
"Ecasound .ewf file mode related variables and faces."
2259
:prefix "ecasound-ewf-"
2262
(defcustom ecasound-ewf-output-device "/dev/dsp"
2263
"*Default output device used for playing .ewf files."
2264
:group 'ecasound-ewf
2267
(defface ecasound-ewf-keyword-face '((t (:foreground "IndianRed")))
2268
"The face used for highlighting keywords."
2269
:group 'ecasound-ewf)
2271
(defface ecasound-ewf-time-face '((t (:foreground "Cyan")))
2272
"The face used for highlighting time information."
2273
:group 'ecasound-ewf)
2275
(defface ecasound-ewf-file-face '((t (:foreground "Green")))
2276
"The face used for highlighting the filname."
2277
:group 'ecasound-ewf)
2279
(defface ecasound-ewf-boolean-face '((t (:foreground "Orange")))
2280
"The face used for highlighting boolean values."
2281
:group 'ecasound-ewf)
2283
(defvar ecasound-ewf-mode-map
2284
(let ((map (make-sparse-keymap)))
2285
(define-key map "\t" 'pcomplete)
2286
(define-key map "\C-c\C-p" 'ecasound-ewf-play)
2288
"Keymap for `ecasound-ewf-mode'.")
2290
(defvar ecasound-ewf-mode-syntax-table
2291
(let ((st (make-syntax-table)))
2292
(modify-syntax-entry ?# "<" st)
2293
(modify-syntax-entry ?\n ">" st)
2295
"Syntax table for `ecasound-ewf-mode'.")
2297
(defvar ecasound-ewf-font-lock-keywords
2298
'(("^\\s-*\\(source\\)[^=]+=\\s-*\\(.*\\)$"
2299
(1 'ecasound-ewf-keyword-face)
2300
(2 'ecasound-ewf-file-face))
2301
("^\\s-*\\(offset\\)[^=]+=\\s-*\\([0-9.]+\\)$"
2302
(1 'ecasound-ewf-keyword-face)
2303
(2 'ecasound-ewf-time-face))
2304
("^\\s-*\\(start-position\\)[^=]+=\\s-*\\([0-9.]+\\)$"
2305
(1 'ecasound-ewf-keyword-face)
2306
(2 'ecasound-ewf-time-face))
2307
("^\\s-*\\(length\\)[^=]+=\\s-*\\([0-9.]+\\)$"
2308
(1 'ecasound-ewf-keyword-face)
2309
(2 'ecasound-ewf-time-face))
2310
("^\\s-*\\(looping\\)[^=]+=\\s-*\\(true\\|false\\)$"
2311
(1 'ecasound-ewf-keyword-face)
2312
(2 'ecasound-ewf-boolean-face)))
2313
"Keyword highlighting specification for `ecasound-ewf-mode'.")
2316
(define-derived-mode ecasound-ewf-mode fundamental-mode "EWF"
2317
"A major mode for editing ecasound .ewf files."
2318
(set (make-local-variable 'comment-start) "# ")
2319
(set (make-local-variable 'comment-start-skip) "#+\\s-*")
2320
(set (make-local-variable 'font-lock-defaults)
2321
'(ecasound-ewf-font-lock-keywords))
2322
(ecasound-ewf-setup-pcomplete))
2324
;;; .ewf-mode pcomplete support
2326
(defun ecasound-ewf-keyword-completion-function ()
2328
(list "source" "offset" "start-position" "length" "looping")))
2330
(defun pcomplete/ecasound-ewf-mode/source ()
2331
(pcomplete-here (pcomplete-entries)))
2333
(defun pcomplete/ecasound-ewf-mode/offset ()
2334
(message "insert audio object at offset (seconds) [read,write]")
2335
(throw 'pcompleted t))
2337
(defun pcomplete/ecasound-ewf-mode/start-position ()
2338
(message "start offset inside audio object (seconds) [read]")
2339
(throw 'pcompleted t))
2341
(defun pcomplete/ecasound-ewf-mode/length ()
2342
(message "how much of audio object data is used (seconds) [read]")
2343
(throw 'pcompleted t))
2345
(defun pcomplete/ecasound-ewf-mode/looping ()
2346
(pcomplete-here (list "true" "false")))
2348
(defun ecasound-ewf-parse-arguments ()
2349
"Parse whitespace separated arguments in the current region."
2350
(let ((begin (save-excursion (beginning-of-line) (point)))
2355
(while (< (point) end)
2356
(skip-chars-forward " \t\n=")
2357
(setq begins (cons (point) begins))
2360
(skip-chars-forward "^ \t\n=")
2361
(if (eq (char-before) ?\\)
2362
(skip-chars-forward " \t\n=")
2364
(setq args (cons (buffer-substring-no-properties
2365
(car begins) (point))
2367
(cons (reverse args) (reverse begins)))))
2369
(defun ecasound-ewf-setup-pcomplete ()
2370
(set (make-local-variable 'pcomplete-parse-arguments-function)
2371
'ecasound-ewf-parse-arguments)
2372
(set (make-local-variable 'pcomplete-command-completion-function)
2373
'ecasound-ewf-keyword-completion-function)
2374
(set (make-local-variable 'pcomplete-command-name-function)
2376
(pcomplete-arg 'first)))
2377
(set (make-local-variable 'pcomplete-arg-quote-list)
2380
;;; Interactive commands
2382
;; FIXME: Make it use ECI.
2383
(defun ecasound-ewf-play ()
2385
(let ((ecasound-arguments (list "-c"
2386
"-i" buffer-file-name
2387
"-o" ecasound-ewf-output-device)))
2388
(and (buffer-modified-p)
2389
(y-or-n-p "Save file before playing? ")
2391
(ecasound "*Ecasound-ewf Player*")))
2393
(add-to-list 'auto-mode-alist (cons "\\.ewf$" 'ecasound-ewf-mode))
2396
;; mode: outline-minor
2397
;; outline-regexp: ";;;;* \\|"
2402
;;; ecasound.el ends here