~ubuntu-branches/ubuntu/jaunty/ecasound2.2/jaunty

« back to all changes in this revision

Viewing changes to ecatools/ecasound.el

  • Committer: Bazaar Package Importer
  • Author(s): Junichi Uekawa
  • Date: 2005-04-14 09:15:48 UTC
  • Revision ID: james.westby@ubuntu.com-20050414091548-o7kgb47z0tcunh0s
Tags: upstream-2.4.1
ImportĀ upstreamĀ versionĀ 2.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
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.
 
273
 
 
274
Variables in this group affect inferior ecasound processes started from
 
275
within Emacs using the command `ecasound'.
 
276
 
 
277
See the subgroup `eci' for settings which affect the programmatic interface
 
278
to 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.
 
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."
 
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).
 
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
 
328
results."
 
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.
 
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."
 
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.
 
401
This 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.
 
412
This means the loglevel, msgsize and return type will get removed if
 
413
parsed 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.
 
424
Arguments 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.
 
430
Argument 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
 
 
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.
 
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.
 
683
Use 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.
 
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
 
743
this 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.
 
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'.
 
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.
 
822
This 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.
 
837
This marker is advanced everytime a successful parse happens."))
 
838
 
 
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))))
 
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.
 
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
 
878
result 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.
 
891
Each key is a type, and the values are Lisp expressions.  During evaluation
 
892
the 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.
 
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
 
912
a string.
 
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)))
 
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.
 
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)))))
 
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.
 
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
 
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.
 
1065
It has the form
 
1066
 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
 
1067
 
 
1068
Use `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.
 
1074
It has the form
 
1075
 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
 
1076
 
 
1077
Use `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.
 
1083
It has the form
 
1084
 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
 
1085
 
 
1086
Use `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.
 
1092
It has the form
 
1093
 ((NAME PREFIX DESCR ((ARGNAME DESCR DEFAULT LOW HIGH TYPE) ...)) ...)
 
1094
 
 
1095
Use `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
 
1161
variable."
 
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 --- The Ecasound Control Interface
 
1178
 
 
1179
(defgroup eci nil
 
1180
  "Ecasound Control Interface."
 
1181
  :group 'ecasound)
 
1182
 
 
1183
(defcustom eci-program (or (getenv "ECASOUND") "ecasound")
 
1184
  "*Program to invoke when doing `eci-init'."
 
1185
  :group 'eci
 
1186
  :type '(choice string (cons string string)))
 
1187
 
 
1188
(defcustom eci-arguments '("-c" "-D" "-d:256")
 
1189
  "*Arguments used by `eci-init'."
 
1190
  :group 'eci
 
1191
  :type 'ecasound-args)
 
1192
 
 
1193
(defvar eci-hide-output nil
 
1194
  "If non-nil, `eci-command' will remove the output generated.")
 
1195
 
 
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))
 
1203
       ,eci-call)))
 
1204
 
 
1205
(defun eci-init ()
 
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
 
1209
created buffer.
 
1210
The caller is responsible for terminating the subprocess at some point."
 
1211
  (save-excursion
 
1212
    (set-buffer
 
1213
     (apply 'make-comint
 
1214
            "eci-ecasound"
 
1215
            eci-program
 
1216
            nil
 
1217
            eci-arguments))
 
1218
    (ecasound-iam-mode)
 
1219
    (while (accept-process-output (get-buffer-process (current-buffer)) 1))
 
1220
    (if (eci-command "int-output-mode-wellformed")
 
1221
        (current-buffer))))
 
1222
 
 
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
 
1226
to `ecasound'."
 
1227
  (interactive)
 
1228
  (switch-to-buffer (eci-init)))
 
1229
 
 
1230
(defun ecasound-find-buffer (buffer-or-process)
 
1231
  (cond
 
1232
   ((bufferp buffer-or-process)
 
1233
    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)))
 
1238
    (current-buffer))
 
1239
   (t (error "Could not determine suitable ecasound buffer"))))
 
1240
 
 
1241
(defun ecasound-find-parent (buffer-or-process)
 
1242
  (with-current-buffer (ecasound-find-buffer buffer-or-process)
 
1243
    (if ecasound-parent
 
1244
        ecasound-parent
 
1245
      (current-buffer))))
 
1246
 
 
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))
 
1264
        (insert command)
 
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))
 
1270
          (setq result
 
1271
                (if eci-int-output-mode-wellformed-flag
 
1272
                    eci-result
 
1273
                  ;; Backward compatibility.  Just return the string
 
1274
                  (buffer-substring-no-properties here (save-excursion
 
1275
                                        ; Strange hack to avoid fields
 
1276
                                                         (forward-char -1)
 
1277
                                                         (beginning-of-line)
 
1278
                                                         (if (not (= here (point)))
 
1279
                                                             (forward-char -1))
 
1280
                                                         (point)))))
 
1281
          (if moving (goto-char (point-max)))
 
1282
          (when (and eci-hide-output result)
 
1283
            (ecasound-delete-last-in-and-output))
 
1284
          result)))))
 
1285
 
 
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"))
 
1289
 
 
1290
;;; ECI commands implemented as lisp functions
 
1291
 
 
1292
(defeci int-cmd-list ()
 
1293
  ""
 
1294
  :cache ecasound-iam-commands
 
1295
  :cache-doc "Available Ecasound IAM commands.")
 
1296
 
 
1297
(defeci run)
 
1298
 
 
1299
(defeci start)
 
1300
 
 
1301
(defeci cs-add ((chainsetup "sChainsetup to add: " "%s"))
 
1302
  "Adds a new chainsetup with name `name`."
 
1303
  :pcomplete doc)
 
1304
 
 
1305
(defeci cs-connect ()
 
1306
  "Connect currently selected chainsetup to engine."
 
1307
  :pcomplete doc)
 
1308
 
 
1309
(defeci cs-connected ()
 
1310
  "Returns the name of currently connected chainsetup."
 
1311
  :pcomplete doc)
 
1312
 
 
1313
(defeci cs-disconnect ()
 
1314
  "Disconnect currently connected chainsetup."
 
1315
  :pcomplete doc)
 
1316
 
 
1317
(defeci cs-forward
 
1318
  ((seconds
 
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")))
 
1323
 
 
1324
(defeci cs-get-length ()
 
1325
  ""
 
1326
  :alias get-length)
 
1327
 
 
1328
(defeci cs-get-length-samples ()
 
1329
  ""
 
1330
  :alias get-length-samples)
 
1331
 
 
1332
(defeci cs-get-position ()
 
1333
  ""
 
1334
  :alias (cs-getpos getpos get-position))
 
1335
 
 
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."
 
1342
  :alias cs-iselect)
 
1343
 
 
1344
(defeci cs-is-valid ()
 
1345
  "Whether currently selected chainsetup is valid (=can be connected)?"
 
1346
  :pcomplete doc
 
1347
  (let ((val (eci-command "cs-is-valid" buffer-or-process)))
 
1348
    (if (interactive-p)
 
1349
        (message "Chainsetup is%s valid" (if (= val 0) "" " not")))
 
1350
    val))
 
1351
 
 
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)
 
1355
    (1 t)
 
1356
    (0 nil)
 
1357
    (otherwise (error "Unexcpected return value from cs-is-valid"))))
 
1358
 
 
1359
(defeci cs-list ()
 
1360
  "Returns a list of all chainsetups."
 
1361
  :pcomplete doc
 
1362
  (let ((val (eci-command "cs-list" buffer-or-process)))
 
1363
    (if (interactive-p)
 
1364
        (message (concat "Available chainsetups: "
 
1365
                         (mapconcat #'identity val ", "))))
 
1366
    val))
 
1367
 
 
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)))
 
1372
 
 
1373
(defeci cs-remove ()
 
1374
  "Removes currently selected chainsetup."
 
1375
  :pcomplete doc)
 
1376
 
 
1377
(defeci cs-rewind
 
1378
  ((seconds
 
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."
 
1383
  :pcomplete doc
 
1384
  :alias (rewind rw))
 
1385
 
 
1386
(defeci cs-save)
 
1387
 
 
1388
(defeci cs-save-as ((filename "FChainsetup filename: " "%s"))
 
1389
  "Saves currently selected chainsetup to file FILENAME."
 
1390
  :pcomplete (pcomplete-here (pcomplete-entries)))
 
1391
 
 
1392
(defeci cs-selected ()
 
1393
  "Returns the name of currently selected chainsetup."
 
1394
  :pcomplete doc
 
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)))))
 
1398
    (if (interactive-p)
 
1399
        (message "Selected chainsetup: %s" val))
 
1400
    val))
 
1401
 
 
1402
(defeci cs-set-position
 
1403
  ((pos
 
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
 
1409
affected."
 
1410
  :alias (cs-setpos setpos set-position))
 
1411
  
 
1412
(defeci cs-status)
 
1413
 
 
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 ','.")
 
1417
 
 
1418
(defeci c-clear ()
 
1419
  "Clear selected chains by removing all chain operators and controllers.
 
1420
Doesn't change how chains are connected to inputs and outputs."
 
1421
  :pcomplete doc)
 
1422
 
 
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."
 
1426
  (let ((avail list)
 
1427
        result current)
 
1428
    (while
 
1429
        (and avail
 
1430
             (not
 
1431
              (string=
 
1432
               (setq current (completing-read prompt (mapcar #'list avail)))
 
1433
               "")))
 
1434
      (setq result (cons current result)
 
1435
            avail (delete current avail)))
 
1436
    (nreverse result)))
 
1437
 
 
1438
(defeci c-deselect
 
1439
  ((chains (ecasound-read-list "Chain to deselect: " (eci-c-selected)) "%s"))
 
1440
  "Deselects chains."
 
1441
  :pcomplete (while (pcomplete-here (eci-c-selected))))
 
1442
 
 
1443
(defeci c-list ()
 
1444
  "Returns a list of all chains.")
 
1445
 
 
1446
(defeci c-mute ()
 
1447
  "Toggle chain muting.  When chain is muted, all data that goes
 
1448
through is muted."
 
1449
  :pcomplete doc)
 
1450
 
 
1451
(defeci c-select ((chains (ecasound-read-list "Chain: " (eci-c-list)) "%s"))
 
1452
  "Selects chains.  Other chains are automatically deselected."
 
1453
  :pcomplete doc)
 
1454
 
 
1455
(defeci c-selected ()
 
1456
  ""
 
1457
  (let ((val (eci-command "c-selected" buffer-or-process)))
 
1458
    (if (interactive-p)
 
1459
        (if (null val)
 
1460
            (message "No selected chains")
 
1461
          (message (concat "Selected chains: "
 
1462
                           (mapconcat #'identity val ", ")))))
 
1463
    val))
 
1464
 
 
1465
(defeci c-select-all ()
 
1466
  "Selects all chains."
 
1467
  :pcomplete doc)
 
1468
 
 
1469
(defeci cs-select
 
1470
  ((chainsetup
 
1471
    (completing-read "Chainsetup: " (mapcar #'list (eci-cs-list)))
 
1472
    "%s"))
 
1473
  ""
 
1474
  :pcomplete (pcomplete-here (eci-hide-output eci-cs-list)))
 
1475
 
 
1476
(defeci ai-add
 
1477
  ((ifstring
 
1478
    (let ((file (read-file-name "Input filename: ")))
 
1479
      (if (file-exists-p file)
 
1480
          (expand-file-name file)
 
1481
        file))
 
1482
    "%s"))
 
1483
  "Adds a new input object."
 
1484
  :pcomplete (pcomplete-here (ecasound-input-file-or-device)))
 
1485
 
 
1486
(defeci ai-attach ()
 
1487
  "Attaches the currently selected audio input object to all selected chains."
 
1488
  :pcomplete doc)
 
1489
 
 
1490
(defeci ai-forward
 
1491
  ((seconds
 
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)."
 
1498
  :pcomplete doc
 
1499
  :alias ai-fw)
 
1500
 
 
1501
(defeci ai-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 input object is rewinded by SECONDS.
 
1508
Time 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 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."
 
1518
  :pcomplete doc
 
1519
  :alias ai-iselect)
 
1520
 
 
1521
(defeci ai-list)
 
1522
 
 
1523
(defeci ai-remove ()
 
1524
  "Removes the currently selected audio input object from the chainsetup."
 
1525
  :pcomplete doc)
 
1526
(defeci ao-remove ()
 
1527
  "Removes the currently selected audio output object from the chainsetup."
 
1528
  :pcomplete doc)
 
1529
 
 
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)))
 
1537
 
 
1538
(defeci ai-selected ()
 
1539
  "Returns the name of the currently selected audio input object."
 
1540
  :pcomplete doc)
 
1541
 
 
1542
(defeci ao-add ((filename "FOutput filename: " "%s"))
 
1543
  ""
 
1544
  :pcomplete (pcomplete-here (ecasound-input-file-or-device)))
 
1545
 
 
1546
(defeci ao-add-default)
 
1547
 
 
1548
(defeci ao-attach ()
 
1549
  "Attaches the currently selected audio output object to all selected chains."
 
1550
  :pcomplete doc)
 
1551
 
 
1552
(defeci ao-forward
 
1553
  ((seconds
 
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)."
 
1560
  :pcomplete doc
 
1561
  :alias ao-fw)
 
1562
 
 
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."
 
1569
  :pcomplete doc
 
1570
  :alias ao-iselect)
 
1571
 
 
1572
(defeci ao-list)
 
1573
 
 
1574
(defeci ao-rewind
 
1575
  ((seconds
 
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)."
 
1582
  :pcomplete doc
 
1583
  :alias ai-rw)
 
1584
 
 
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)))
 
1591
 
 
1592
(defeci ao-selected ()
 
1593
  "Returns the name of the currently selected audio output object."
 
1594
  :pcomplete doc)
 
1595
 
 
1596
(defeci engine-status ()
 
1597
  "Returns a string describing the engine status
 
1598
\(running, stopped, finished, error, not ready)."
 
1599
  :pcomplete doc
 
1600
  (with-current-buffer (ecasound-find-parent buffer-or-process)
 
1601
    (setq eci-engine-status (eci-command "engine-status" buffer-or-process))))
 
1602
 
 
1603
(defmacro ecasound-complete-cop-map (map)
 
1604
  (let ((m (intern (format "eci-map-%S-list" map))))
 
1605
    `(progn
 
1606
       (cond
 
1607
        ((= pcomplete-last 2)
 
1608
         (pcomplete-next-arg)
 
1609
         (pcomplete-here
 
1610
          (sort (mapcar (lambda (elt) (nth 1 elt))
 
1611
                        (eci-hide-output ,m))
 
1612
                #'string-lessp)))
 
1613
        ((> pcomplete-last 2)
 
1614
         (ecasound-echo-arg
 
1615
          (nth (- pcomplete-last 3)
 
1616
               (eci-map-find-args
 
1617
                (pcomplete-arg -1) (eci-hide-output ,m)))))))))
 
1618
 
 
1619
(defeci cop-add
 
1620
  ((string
 
1621
    (if current-prefix-arg
 
1622
        (read-string "Chainop to add: " "-")
 
1623
      (let* ((cop
 
1624
              (completing-read
 
1625
               "Chain operator: "
 
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)))
 
1633
        (concat
 
1634
         (cond
 
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) ","))))
 
1642
    "%s"))
 
1643
  ""
 
1644
  :pcomplete
 
1645
  (progn
 
1646
    (cond
 
1647
     ((= pcomplete-last 1)
 
1648
      (pcomplete-here
 
1649
       (append
 
1650
        '("-el:" "-pn:")
 
1651
        (mapcar
 
1652
         (lambda (elt)
 
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)
 
1660
      (ecasound-echo-arg
 
1661
       (nth (- pcomplete-last 2)
 
1662
            (eci-map-find-args
 
1663
             (substring (pcomplete-arg) 1)
 
1664
             (eci-hide-output eci-map-cop-list))))))
 
1665
    (throw 'pcompleted t)))
 
1666
 
 
1667
(defeci cop-list)
 
1668
 
 
1669
(defeci cop-remove)
 
1670
 
 
1671
(defeci cop-select
 
1672
  ((index "nChainop to select: " "%d")))
 
1673
 
 
1674
(defeci cop-selected)
 
1675
 
 
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."
 
1682
  :pcomplete doc)
 
1683
 
 
1684
(defeci ctrl-add
 
1685
  ((string
 
1686
    (if current-prefix-arg
 
1687
        (read-string "Controller to add: " "-")
 
1688
      (let ((ctrl (assoc
 
1689
                   (completing-read
 
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) ","))))
 
1695
    "%s")))
 
1696
 
 
1697
(defeci ctrl-select
 
1698
  ((index "nController to select: " "%d")))
 
1699
 
 
1700
(defeci copp-select
 
1701
  ((index "nChainop parameter to select: " "%d")))
 
1702
 
 
1703
(defeci copp-get)
 
1704
 
 
1705
(defeci copp-set
 
1706
  ((value "nValue for Chain operator parameter: " "%f")))
 
1707
 
 
1708
;;;; ECI Examples
 
1709
 
 
1710
(defun eci-example ()
 
1711
  "Implements the example given in the ECI documentation."
 
1712
  (interactive)
 
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)
 
1722
    (eci-cs-connect)
 
1723
    (eci-command "start")
 
1724
    (sit-for 1)
 
1725
    (while (and (string= (eci-engine-status) "running")
 
1726
                (< (eci-get-position) 15))
 
1727
      (eci-copp-set (+ (eci-copp-get) 500))
 
1728
      (sit-for 1))
 
1729
    (eci-command "stop")
 
1730
    (eci-cs-disconnect)
 
1731
    (message (concat "Chain operator status: "
 
1732
                      (eci-command "cop-status")))))
 
1733
 
 
1734
(defun eci-make-temp-file-name (suffix)
 
1735
  (concat (make-temp-name
 
1736
           (expand-file-name "emacs-eci" temporary-file-directory))
 
1737
          suffix))
 
1738
 
 
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 "")))
 
1744
        result
 
1745
      default)))
 
1746
 
 
1747
;;; Markers
 
1748
 
 
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."))
 
1753
 
 
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)))
 
1761
    (if (not e)
 
1762
        (setq ecasound-markers (cons (cons chainsetup (list (cons name pos)))
 
1763
                                     ecasound-markers))
 
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)))
 
1768
 
 
1769
(defun ecasound-goto-mark (name)
 
1770
  "Set the position previously recorded as NAME."
 
1771
  (interactive
 
1772
   (list
 
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)))
 
1777
    (if (not e)
 
1778
        (message "No marks set for chainsetup %s" cs)
 
1779
      (let ((mark (assoc name (cdr e))))
 
1780
        (if (not mark)
 
1781
            (message "Mark %s is not set for chainsetup %s" name cs)
 
1782
          (eci-cs-set-position (cdr mark)))))))
 
1783
 
 
1784
;;; Ecasound Signalview
 
1785
 
 
1786
(defconst ecasound-signalview-clipped-threshold (- 1.0 (/ 1.0 16384)))
 
1787
 
 
1788
(defconst ecasound-signalview-bar-length 55)
 
1789
 
 
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"
 
1795
                     (/ secs 60)
 
1796
                     (% (round (floor secs)) 60)
 
1797
                     (* (- secs (floor secs)) 100))))
 
1798
    (if long
 
1799
        str
 
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 ?.))))
 
1804
          (incf idx))
 
1805
        (substring str idx)))))
 
1806
 
 
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."
 
1811
  (interactive
 
1812
   (list
 
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)
 
1818
        file))
 
1819
    (ecasound-read-from-minibuffer "Output" "null")))
 
1820
  (let* (;; THis saves time
 
1821
         (ecasound-parse-cleanup-buffer nil)
 
1822
         (handle (eci-init))
 
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*"))
 
1834
    (erase-buffer)
 
1835
    (dotimes (ch channels)
 
1836
      (insert "---\n"))
 
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
 
1844
          (list
 
1845
           (list
 
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)
 
1852
    (eci-start handle)
 
1853
    (sit-for 0.8)
 
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))
 
1866
            (insert
 
1867
             (concat
 
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)))
 
1877
      (sit-for 0.1)
 
1878
      (fit-window-to-buffer))
 
1879
    (goto-char (point-max))
 
1880
    (let ((pos (point)))
 
1881
      (insert
 
1882
       (nth 2
 
1883
            (nth 2
 
1884
                 (nthcdr 2
 
1885
                         (assoc "Volume analysis"
 
1886
                                (assoc "analysis"
 
1887
                                       (eci-cop-status handle)))))))
 
1888
      (goto-char pos))
 
1889
    (recenter channels)
 
1890
    (fit-window-to-buffer)))
 
1891
 
 
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")))
 
1896
    (unwind-protect
 
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)
 
1901
          (eci-cop-add "-ev")
 
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)))
 
1906
            (eci-cs-disconnect)
 
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")
 
1912
              (eci-cop-select 1)
 
1913
              (eci-copp-select 1)
 
1914
              (eci-copp-set (* gainfactor 100))
 
1915
              (eci-cs-connect) (eci-run) (eci-cs-disconnect)
 
1916
              (message "Done"))))
 
1917
      (if (file-exists-p tmpfile)
 
1918
          (delete-file tmpfile)))))
 
1919
 
 
1920
;;; Utility functions for converting strings to data-structures.
 
1921
 
 
1922
(defvar eci-cop-status-header
 
1923
  "### Chain operator status (chainsetup '\\([^']+\\)') ###\n")
 
1924
 
 
1925
(defun eci-process-cop-status (string)
 
1926
  (with-temp-buffer
 
1927
    (insert string) (goto-char (point-min))
 
1928
    (when (re-search-forward eci-cop-status-header nil t)
 
1929
      (let (result)
 
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))
 
1936
                    (args
 
1937
                     (mapcar
 
1938
                      (lambda (elt)
 
1939
                        (when (string-match
 
1940
                               "\\[\\([0-9]+\\)\\] \\(.*\\) \\([0-9.-]+\\)$"
 
1941
                               elt)
 
1942
                          (list (match-string-no-properties 2 elt)
 
1943
                                (string-to-number (match-string 1 elt))
 
1944
                                (string-to-number (match-string 3 elt)))))
 
1945
                      (split-string
 
1946
                       (match-string-no-properties 3) ", "))))
 
1947
                (if (looking-at "\tStatus info:\n")
 
1948
                    (setq args
 
1949
                          (append
 
1950
                           args
 
1951
                           (list
 
1952
                            (list
 
1953
                             "Status info" nil
 
1954
                             (buffer-substring
 
1955
                              (progn (forward-line 1) (point))
 
1956
                              (or (re-search-forward "\n\n" nil t)
 
1957
                                  (point-max))))))))
 
1958
                (setq chain (cons (append (list name n) args) chain))))
 
1959
            (setq result (cons (reverse (append chain (list c))) result))))
 
1960
        result))))
 
1961
 
 
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."
 
1965
  (mapcar
 
1966
   (lambda (elt)
 
1967
     (append
 
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))
 
1971
        (while (> count 0)
 
1972
          (setq
 
1973
           res
 
1974
           (cons
 
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)))
 
1981
                  (cond
 
1982
                   ((string= (nth 7 elt) "1")
 
1983
                    'toggle)
 
1984
                   ((string= (nth 8 elt) "1")
 
1985
                    'integer)
 
1986
                   ((string= (nth 9 elt) "1")
 
1987
                    'logarithmic)
 
1988
                   ((string= (nth 10 elt) "1")
 
1989
                    'output))) res)
 
1990
           elt (nthcdr 11 elt)
 
1991
           count (1- count)))
 
1992
        (reverse res))))
 
1993
   (mapcar (lambda (str) (split-string str ","))
 
1994
           (split-string string "\n"))))
 
1995
 
 
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\"."
 
2001
  :pcomplete doc)
 
2002
 
 
2003
(defeci cop-register)
 
2004
(defeci preset-register)
 
2005
(defeci ctrl-register)
 
2006
 
 
2007
(defeci cop-status)
 
2008
 
 
2009
(defeci ladspa-register)
 
2010
 
 
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)))
 
2015
         (answer
 
2016
          (read-from-minibuffer
 
2017
           (concat
 
2018
            (car copp)
 
2019
            " (default " default "): ")
 
2020
           nil nil nil nil
 
2021
           default)))
 
2022
    (if (and answer (not (string= answer "")))
 
2023
        answer
 
2024
      default)))
 
2025
 
 
2026
;;; ChainOp Editor
 
2027
 
 
2028
(defvar ecasound-cop-edit-mode-map
 
2029
  (let ((map (make-keymap)))
 
2030
    (set-keymap-parent map widget-keymap)
 
2031
    map))
 
2032
 
 
2033
(define-derived-mode ecasound-cop-edit-mode fundamental-mode "COP-edit"
 
2034
  "A major mode for editing ecasound chain operators.")
 
2035
 
 
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."
 
2039
  (interactive)
 
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)
 
2044
    (mapc
 
2045
     (lambda (chain)
 
2046
       (widget-insert (format "Chain %s:\n" (car chain)))
 
2047
       (mapc
 
2048
        (lambda (cop)
 
2049
          (apply 'widget-create 'ecasound-cop :buffer cb cop))
 
2050
        (cdr chain)))
 
2051
     chains)
 
2052
    (widget-setup)
 
2053
    (goto-char (point-min))))
 
2054
 
 
2055
(define-widget 'ecasound-cop 'default
 
2056
  "A Chain Operator.
 
2057
:children is a list of ecasound-copp widgets."
 
2058
  :convert-widget
 
2059
  (lambda (widget)
 
2060
    (let ((args (widget-get widget :args)))
 
2061
      (when args
 
2062
        (widget-put widget :tag (car args))
 
2063
        (widget-put widget :cop-number (nth 1 args))
 
2064
        (widget-put widget :args (cddr args))))
 
2065
    widget)
 
2066
  :value-create
 
2067
  (lambda (widget)
 
2068
    (widget-put
 
2069
     widget :children
 
2070
     (mapcar
 
2071
      (lambda (copp-arg)
 
2072
        (apply 'widget-create-child-and-convert
 
2073
             widget '(ecasound-copp) copp-arg))
 
2074
      (widget-get widget :args))))
 
2075
  :format-handler
 
2076
  (lambda (widget escape)
 
2077
    (cond
 
2078
     ((eq escape ?i)
 
2079
      (widget-put
 
2080
       widget :cop-select
 
2081
       (widget-create-child-value
 
2082
        widget '(ecasound-cop-select) (widget-get widget :cop-number))))))
 
2083
  :format "%i %t\n%v")
 
2084
 
 
2085
(define-widget 'ecasound-cop-select 'link
 
2086
  "Select this chain operator parameter."
 
2087
  :help-echo "RET to select."
 
2088
  :button-prefix ""
 
2089
  :button-suffix ""
 
2090
  :format "%[%v.%]"
 
2091
  :action
 
2092
  (lambda (widget &rest ignore)
 
2093
    (let ((buffer (widget-get (widget-get widget :parent) :buffer)))
 
2094
      (eci-cop-select (widget-value widget) buffer))))
 
2095
 
 
2096
;;;; A Chain Operator Parameter Widget.
 
2097
 
 
2098
; This is used as a component of the cop widget.
 
2099
 
 
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
 
2106
  :size 10)
 
2107
 
 
2108
(defun ecasound-copp-convert (widget)
 
2109
  "Convert args."
 
2110
  (let ((args (widget-get widget :args)))
 
2111
    (when 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)))
 
2116
  widget)
 
2117
 
 
2118
(defun ecasound-copp-format-handler (widget escape)
 
2119
  (cond
 
2120
   ((eq escape ?i)
 
2121
    (widget-put
 
2122
     widget
 
2123
     :copp-select
 
2124
     (widget-create-child-value
 
2125
      widget
 
2126
      '(ecasound-copp-select)
 
2127
      (widget-get widget :copp-number))))
 
2128
   ((eq escape ?s)
 
2129
    (widget-put
 
2130
     widget
 
2131
     :slider
 
2132
     (widget-create-child-value
 
2133
      widget
 
2134
      '(slider)
 
2135
      (string-to-number (widget-get widget :value)))))))
 
2136
 
 
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))
 
2141
        (progn
 
2142
          (eci-cop-set (widget-get (widget-get widget :parent) :cop-number)
 
2143
                       (widget-get widget :copp-number)
 
2144
                       (widget-value widget)
 
2145
                       buffer))
 
2146
      (message "Invalid"))))
 
2147
 
 
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)
 
2152
    map)
 
2153
  "Keymap used inside an copp.")
 
2154
 
 
2155
(defun ecasound-copp-increase (pos &optional event)
 
2156
  (interactive "@d")
 
2157
  ;; BUG, if we do this, the field is suddently no longer editable, why???
 
2158
  (let ((widget (widget-get (widget-at pos) :parent)))
 
2159
    (widget-value-set
 
2160
     widget
 
2161
     (+ (widget-value widget) 1))
 
2162
    (widget-apply widget :action)
 
2163
    (widget-setup)))
 
2164
 
 
2165
(defun ecasound-copp-decrease (pos &optional event)
 
2166
  (interactive "@d")
 
2167
  (let ((widget (widget-get (widget-at pos) :parent)))
 
2168
    (widget-value-set
 
2169
     widget
 
2170
     (- (widget-value widget) 1))
 
2171
    (widget-apply widget :action)
 
2172
    (widget-setup)))
 
2173
 
 
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
 
2178
  :format "%[%v%]"
 
2179
  :action 'ecasound-copp-select-action)
 
2180
 
 
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)
 
2184
                            :buffer)))
 
2185
    (eci-copp-select (widget-get widget :value) buffer)))
 
2186
 
 
2187
(define-widget 'slider 'default
 
2188
  "A slider."
 
2189
  :action 'widget-slider-action
 
2190
  :button-prefix ""
 
2191
  :button-suffix ""
 
2192
  :format "(%[%v%])"
 
2193
  :keymap
 
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)
 
2198
    map)
 
2199
  :value-create 'widget-slider-value-create
 
2200
  :value-delete 'ignore
 
2201
  :value-get 'widget-value-value-get
 
2202
  :size 70
 
2203
  :value 0)
 
2204
 
 
2205
(defun widget-slider-press (pos &optional event)
 
2206
  "Invoke slider at POS."
 
2207
  (interactive "@d")
 
2208
  (let ((button (get-char-property pos 'button)))
 
2209
    (if button
 
2210
        (widget-apply-action
 
2211
         (widget-value-set
 
2212
          button
 
2213
          (- pos (overlay-start (widget-get button :button-overlay))))
 
2214
         event)
 
2215
      (let ((command (lookup-key widget-global-map (this-command-keys))))
 
2216
        (when (commandp command)
 
2217
          (call-interactively command))))))
 
2218
 
 
2219
(defun widget-slider-increase (pos &optional event)
 
2220
  "Increase slider at POS."
 
2221
  (interactive "@d")
 
2222
  (widget-slider-change pos #'+ 1 event))
 
2223
 
 
2224
(defun widget-slider-decrease (pos &optional event)
 
2225
  "Decrease slider at POS."
 
2226
  (interactive "@d")
 
2227
  (widget-slider-change pos #'- 1 event))
 
2228
 
 
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)))
 
2232
    (if button
 
2233
        (widget-apply-action
 
2234
         (widget-value-set button (apply function (widget-value button) value))
 
2235
         event)
 
2236
      (let ((command (lookup-key widget-global-map (this-command-keys))))
 
2237
        (when (commandp command)
 
2238
          (call-interactively command))))))
 
2239
 
 
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)))
 
2244
 
 
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))))
 
2249
        (from (point)))
 
2250
    (insert-char ?\  value)
 
2251
    (insert-char ?\| 1)
 
2252
    (insert-char ?\  (- size value 1))))
 
2253
 
 
2254
 
 
2255
;;; Ecasound .ewf major mode
 
2256
 
 
2257
(defgroup ecasound-ewf nil
 
2258
  "Ecasound .ewf file mode related variables and faces."
 
2259
  :prefix "ecasound-ewf-"
 
2260
  :group 'ecasound)
 
2261
 
 
2262
(defcustom ecasound-ewf-output-device "/dev/dsp"
 
2263
  "*Default output device used for playing .ewf files."
 
2264
  :group 'ecasound-ewf
 
2265
  :type 'string)
 
2266
 
 
2267
(defface ecasound-ewf-keyword-face '((t (:foreground "IndianRed")))
 
2268
  "The face used for highlighting keywords."
 
2269
  :group 'ecasound-ewf)
 
2270
 
 
2271
(defface ecasound-ewf-time-face '((t (:foreground "Cyan")))
 
2272
  "The face used for highlighting time information."
 
2273
  :group 'ecasound-ewf)
 
2274
 
 
2275
(defface ecasound-ewf-file-face '((t (:foreground "Green")))
 
2276
  "The face used for highlighting the filname."
 
2277
  :group 'ecasound-ewf)
 
2278
 
 
2279
(defface ecasound-ewf-boolean-face '((t (:foreground "Orange")))
 
2280
  "The face used for highlighting boolean values."
 
2281
  :group 'ecasound-ewf)
 
2282
 
 
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)
 
2287
    map)
 
2288
  "Keymap for `ecasound-ewf-mode'.")
 
2289
 
 
2290
(defvar ecasound-ewf-mode-syntax-table
 
2291
  (let ((st (make-syntax-table)))
 
2292
    (modify-syntax-entry ?# "<" st)
 
2293
    (modify-syntax-entry ?\n ">" st)
 
2294
    st)
 
2295
  "Syntax table for `ecasound-ewf-mode'.")
 
2296
 
 
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'.")
 
2314
 
 
2315
;;;###autoload
 
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))
 
2323
 
 
2324
;;; .ewf-mode pcomplete support
 
2325
 
 
2326
(defun ecasound-ewf-keyword-completion-function ()
 
2327
  (pcomplete-here
 
2328
   (list "source" "offset" "start-position" "length" "looping")))
 
2329
 
 
2330
(defun pcomplete/ecasound-ewf-mode/source ()
 
2331
  (pcomplete-here (pcomplete-entries)))
 
2332
 
 
2333
(defun pcomplete/ecasound-ewf-mode/offset ()
 
2334
  (message "insert audio object at offset (seconds) [read,write]")
 
2335
  (throw 'pcompleted t))
 
2336
 
 
2337
(defun pcomplete/ecasound-ewf-mode/start-position ()
 
2338
  (message "start offset inside audio object (seconds) [read]")
 
2339
  (throw 'pcompleted t))
 
2340
 
 
2341
(defun pcomplete/ecasound-ewf-mode/length ()
 
2342
  (message "how much of audio object data is used (seconds) [read]")
 
2343
  (throw 'pcompleted t))
 
2344
 
 
2345
(defun pcomplete/ecasound-ewf-mode/looping ()
 
2346
  (pcomplete-here (list "true" "false")))
 
2347
 
 
2348
(defun ecasound-ewf-parse-arguments ()
 
2349
  "Parse whitespace separated arguments in the current region."
 
2350
  (let ((begin (save-excursion (beginning-of-line) (point)))
 
2351
        (end (point))
 
2352
        begins args)
 
2353
    (save-excursion
 
2354
      (goto-char begin)
 
2355
      (while (< (point) end)
 
2356
        (skip-chars-forward " \t\n=")
 
2357
        (setq begins (cons (point) begins))
 
2358
        (let ((skip t))
 
2359
          (while skip
 
2360
            (skip-chars-forward "^ \t\n=")
 
2361
            (if (eq (char-before) ?\\)
 
2362
                (skip-chars-forward " \t\n=")
 
2363
              (setq skip nil))))
 
2364
        (setq args (cons (buffer-substring-no-properties
 
2365
                          (car begins) (point))
 
2366
                         args)))
 
2367
      (cons (reverse args) (reverse begins)))))
 
2368
 
 
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)
 
2375
       (lambda ()
 
2376
         (pcomplete-arg 'first)))
 
2377
  (set (make-local-variable 'pcomplete-arg-quote-list)
 
2378
       (list ? )))
 
2379
 
 
2380
;;; Interactive commands
 
2381
 
 
2382
;; FIXME: Make it use ECI.
 
2383
(defun ecasound-ewf-play ()
 
2384
  (interactive)
 
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? ")
 
2390
         (save-buffer))
 
2391
    (ecasound "*Ecasound-ewf Player*")))
 
2392
 
 
2393
(add-to-list 'auto-mode-alist (cons "\\.ewf$" 'ecasound-ewf-mode))
 
2394
 
 
2395
;; Local variables:
 
2396
;; mode: outline-minor
 
2397
;; outline-regexp: ";;;;* \\| "
 
2398
;; End:
 
2399
 
 
2400
(provide 'ecasound)
 
2401
 
 
2402
;;; ecasound.el ends here
 
2403