ecasound.el --- Version 0.6

New Message Reply About this list Date view Thread view Subject view Author view Other groups

Subject: ecasound.el --- Version 0.6
From: Mario Lang (mlang_AT_delysid.org)
Date: Fri Oct 11 2002 - 01:46:11 EEST


Hi!

A new day, a new Emacs-Lisp mile-stone. :)

This time I'll leave it up to you to scroll down some lines and read the
section ChangeLog in the file. But to make my happiness some air,
try the following:

startup ecasound like described in my yesterday mail, and type:
cop-add -el:<TAB>

well, the unbelievable thing happens :).

complete hermesFilter for instance.

then add some args, like
,10,<TAB>

You'll see "LFO1 wave (0 = sin 1 = tri 2 = saw 3 = squ 4 = s&h)"
echoed in the minibuffer.

Isn't this cute? :-O

You can use this on all native chainops and ladspa plugins now, i.e.
-eS:5
if point is after the : and you hit TAB, you get stamp-id echoed
in the minibuffer. This is nice for looking what a particular
number really does.

P.S.: With some wider-testing and some bits of bugfixing pending, I'd like
to shyly ask if ecasound.el could get included in some future release of
ecasound?

;;; ecasound.el --- Interactive and programmatic interface to Ecasound

;; Copyright (C) 2001, 2002 Mario Lang

;; Author: Mario Lang <mlang_AT_delysid.org>
;; Keywords: audio, ecasound, eci, comint, process, pcomplete
;; Version: 0.6

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This file implements several aspects of ecasound use:
;;
;; * A derived-major-mode, from comint mode for an inferior ecasound
;; process (ecasound-aim-mode). Complete with context sensitive
;; completion and interactive features to control the current process
;; using ECI.
;;
;; * Ecasound Control Interface (ECI) library for programmatic control
;; of a Ecasound process. This allows you to write Ecasound batch
;; jobs in Emacs-Lisp with Lisp functions and return values. Have a
;; look at eci-example and ecasound-normalize.
;;
;; * ecasound-ewf-mode, a mode for editing .ewf files.
;;
;; ChangeLog:
;;
;; Version 0.6:
;;
;; * Fix the problem when using eci-* commands while typing a command
;; in the buffer (input area). Now already typed text is preserved.
;;
;; * pcomplete/ecasound-aim-mode/cop-add for completing cop-add in
;; the buffer directly. Only glitch is that it doesn't add ':' and ','
;; at the right place by default. Works for -el: completion too now, can
;; complete all ladspa plugin arguments (it gives help for them).
;;
;; * Completely integrated comint and ECI handling. The main work
;; is now done in the comint-output-filter-function `eci-output-filter'
;; which sets `eci-return-value', `eci-return-type' and `eci-result'
;; if a return value was parsed correctly. Also calls `eci-message-hook'
;; and `eci-error-hook'.
;; This now allows us to be very flexible, i.e., user types a command
;; manually on the ecasound prompt. After he sent it you can use
;; `eci-last-command' to test which command it was, and above variables to
;; see what the result was.
;; `eci-output-filter' now also does automatic handling of cop-register
;; and ladspa-register result values (i.e., sets the buffer-local
;; variables).
;;
;; * `eci-last-command' is used to track the last issued command via
;; `comint-input-filter-functions' (`eci-input-filter').
;; This allows us to catch useful results like those of *-registerr
;; command even if the user issued them manually.
;;
;; * `eci-hide-output': If t, `eci-command' will delete the generated
;; output so that it is not visible in the buffer.
;; This is useful for fetching *-register info while completing without
;; producing unwanted output...
;; Only bug there is that everytime we do this, the window redisplays
;; such that the prompt+point is in the first line (strange).
;;
;; Version 0.5:
;;
;; * ecasound.el, ecasound-ewf.el and eci.el were merged into a single
;; file, ecasound.el. Code was integrated (ecasound-aim-mode uses eci-*
;; functions to offer interactive features).
;;
;; * To work correctly, an ecasound version implementing the
;; int-output-mode-wellformed command is expected. Older versions are
;; only supported in a limited way. So, you probably want to do
;; (setq ecasound-program "/path/to/cvs/built/version")
;; first.
;;
;; * ecasound-cop-add: An interactive version of eci-cop-add, with a
;; completion based menu and separate prompts for arguments. This
;; uses eci-cop-register to get the information and is a first attempt
;; to show how much ecasound.el can actually make ecasound more fun!
;;
;; Todo:
;;
;; * Convince Kai to make NetECI look and behave exactly like ecasound -c.
;; This would make it very easy to make neteci connections using the current
;; code.
;; * Cache things like c- and cs-list locally. Same for engine-status.
;; * Tweak the mode-line to include status informations like engine-status
;; or inputs/outputs/chains or the current selected cs or chains.
;; * Write eci-cop-register alike support for preset-register.
;; * Copy documentation for ECI commands into eci-* docstrings and menu
;; :help keywords.
;; * Expand the menu.
;; * Bind most important interactive functions in ecasound-aim-mode-map
;; (which layout to use?)
;; * Collapse all the duplicate code into a macro and an alist which
;; defines the available eci commands. (This seems rather complicated
;; and is probably not worth the effort.
;;; Code:

(require 'comint)
(require 'easymenu)
(require 'pcomplete)

(defgroup ecasound nil
  "Ecasound is a multitrack audio recorder.

Variables in this group affect inferior ecasound processes started from
within Emacs using the command `ecasound'.

See the subgroup `eci' for settings which affect the programmatic interface
to ECI."
  :prefix "ecasound-"
  :group 'processes)

(defcustom ecasound-arguments '("-c")
  "*Default command line arguments when starting a ecasound process."
  :group 'ecasound
  :type '(repeat string))

(defcustom ecasound-program "/usr/bin/ecasound"
  "*Ecasound's executable.
This program is executed when the user invokes \\[ecasound]."
  :group 'ecasound
  :type 'file)

(defcustom ecasound-prompt-regexp "^ecasound[^>]*> "
  "Regexp to use to match the prompt."
  :group 'ecasound
  :type 'regexp)

(defconst ecasound-iam-commands
  '( ;; GENERAL
    "quit" "q"
    "start" "t"
    "stop" "s"
    "run"
    "debug"
    "help" "h"
    ;; GLOBAL
    "status" "st"
    "engine-status"
    ;; CHAINSETUPS
    "cs-add"
    "cs-remove"
    "cs-list"
    "cs-select"
    "cs-selected"
    "cs-index-select" "cs-iselect"
    "cs-load"
    "cs-save"
    "cs-save-as"
    "cs-edit"
    "cs-is-valid"
    "cs-connect"
    "cs-disconnect"
    "cs-connected"
    "cs-rewind" "rewind" "rw"
    "cs-forward" "forward" "fw"
    "cs-set-position" "cs-setpos" "setpos" "set-position"
    "cs-get-position" "cs-getpos" "getpos" "get-position"
    "cs-get-length" "get-length"
    "cs-set-length"
    "cs-toggle-loop"
    "cs-set-param"
    "cs-set-audio-format"
    "cs-status" "cs"
    ;; CHAINS
    "c-add"
    "c-remove"
    "c-list"
    "c-select"
    "c-index-select" "c-iselect"
    "c-select-all"
    "c-select-add"
    "c-deselect"
    "c-selected"
    "c-clear"
    "c-rename"
    "c-muting"
    "c-bypass"
    "c-forward" "c-fw"
    "c-rewind" "c-rw"
    "c-set-position" "c-setpos"
    "c-status"
    ;; AUDIO INPUT/OUTPUT OBJECTS
    "ai-add"
    "ao-add"
    "ai-select"
    "ao-select"
    "ai-index-select" "ai-iselect"
    "ao-index-select" "ao-iselect"
    "ai-selected"
    "ao-selected"
    "ai-attach"
    "ao-attach"
    "ai-remove"
    "ao-remove"
    "ai-forward" "ai-fw"
    "ao-forward" "ao-fw"
    "ai-rewind" "ai-rw"
    "ao-rewind" "ao-rw"
    "ai-set-position" "ai-setpos"
    "ao-set-position" "ao-setpos"
    "ai-get-position" "ai-getpos"
    "ao-get-position" "ao-getpos"
    "ai-get-length"
    "ao-get-length"
    "ai-get-format"
    "ao-get-format"
    "ai-wave-edit"
    "ao-wave-edit"
    "ai-list"
    "ao-list"
    "aio-register"
    "aio-status"
    ;; CHAIN OPERATORS
    "cop-add"
    "cop.list"
    "cop-remove"
    "cop-select"
    "cop-index-select" "cop-iselect"
    "cop-selected"
    "cop-set"
    "cop-status"
    "copp-list"
    "copp-select"
    "copp-index-select" "copp-iselect"
    "copp-selected"
    "copp-set"
    "copp-get"
    "cop-register"
    "preset-register"
    "ladspa-register"
    ;; CONTROLLERS
    "ctrl-add"
    "ctrl-remove"
    "ctrl-list"
    "ctrl-select"
    "ctrl-index-select" "ctrl-iselect"
    "ctrl-selected"
    "ctrl-status"
    "ctrl-register"
    ;; OBJECT MAPS
                                        ; Unimplemented currently...
    )
  "Available Ecasound IAM commands.")
  
(defvar ecasound-iam-mode-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map comint-mode-map)
    (define-key map "\t" 'pcomplete)
    (define-key map (kbd "M-\"") 'eci-command)
                                        ; (define-key map "\M-?"
                                        ; 'comint-dynamic-list-filename-completions)
                                        ; (define-key map [menu-bar completion]
                                        ; (copy-keymap (lookup-key comint-mode-map [menu-bar completion])))
    map))

(easy-menu-define
  ecasound-iam-cs-menu ecasound-iam-mode-map
  "Chainsetup menu."
  (list "Chainsetup"
        ["Add..." eci-cs-add t]
        ["Load..." eci-cs-load t]
        ["Save" eci-cs-save t]
        ["Save As..." eci-cs-save-as t]
        ["List" eci-cs-list t]
        ["Select" eci-cs-select t]
        ["Select via index" eci-cs-index-select t]
        "-"
        ["Selected" eci-cs-selected t]
        ["Valid?" eci-cs-is-valid t]
        ["Connect" eci-cs-connect t]
        ["Disconnect" eci-cs-disconnect t]
        ["Get position" eci-cs-get-position t]
        ["Get length" eci-cs-get-length t]
        ["Get length in samples" eci-cs-get-length-samples t]))
(easy-menu-add ecasound-iam-cs-menu ecasound-iam-mode-map)
(easy-menu-define
  ecasound-iam-c-menu ecasound-iam-mode-map
  "Chain menu."
  (list "Chain"
        ["Add..." eci-c-add t]
        ["Select..." eci-c-select t]
        ["Select All" eci-c-select-all t]
        ["Deselect..." eci-c-deselect t]
        ["Selected" eci-c-selected t]
        ))
(easy-menu-add ecasound-iam-c-menu ecasound-iam-mode-map)
(easy-menu-define
  ecasound-iam-cop-menu ecasound-iam-mode-map
  "Chain Operator menu."
  (list "ChainOp"
        ["Add..." ecasound-cop-add t]
        ["Select..." eci-cop-select t]
        "-"
        ["Select parameter..." eci-copp-select t]
        ["Get parameter value" eci-copp-get t]
        ["Set parameter value..." eci-copp-set t]
        ))
(easy-menu-add ecasound-iam-c-menu ecasound-iam-mode-map)

(define-derived-mode ecasound-iam-mode comint-mode "Ecasound-IAM"
  "Special mode for ecasound processes in interactive mode."
  (set (make-local-variable 'comint-prompt-regexp)
       (set (make-local-variable 'paragraph-start)
            ecasound-prompt-regexp))
  (add-hook 'comint-output-filter-functions 'eci-output-filter nil t)
  (add-hook 'comint-input-filter-functions 'eci-input-filter nil t)
  (ecasound-iam-setup-pcomplete))

;;;###autoload
(defun ecasound (&optional buffer)
  "Run an inferior ecasound, with I/O through BUFFER (which defaults to `*ecasound*').
Interactively, a prefix arg means to prompt for BUFFER.
If BUFFER exists but ecasound process is not running, make new ecasound
process using `ecasound-arguments'.
If BUFFER exists and ecasound process is running, just switch to BUFFER.
The buffer is put in ecasound mode, giving commands for sending input and
completing IAM commands. See `ecasound-iam-mode'.

\(Type \\[describe-mode] in the ecasound buffer for a list of commands.)"
  (interactive
   (list
    (and current-prefix-arg
         (read-buffer "Ecasound buffer: " "*ecasound*"))))
  (when (null buffer)
    (setq buffer "*ecasound*"))
  (if (not (comint-check-proc buffer))
      (let (ecasound-buffer)
        (save-excursion
          (set-buffer (apply 'make-comint-in-buffer
                             "ecasound" buffer
                             ecasound-program
                             nil
                             ecasound-arguments))
          (setq ecasound-buffer (current-buffer))
          (ecasound-iam-mode)
          (while (accept-process-output
                  (get-buffer-process (current-buffer))
                  1))
          (if (not (eq (eci-command "int-output-mode-wellformed") t))
              (message "Failed to initialize properly")))
        (pop-to-buffer ecasound-buffer))
    (pop-to-buffer buffer)))

(defun ecasound-delete-last-in-and-output ()
  "Delete the region of text generated by the last in and output.
This is usually used to hide ECI requests from the user."
  (delete-region
   (save-excursion (goto-char comint-last-input-end) (forward-line -1)
                   (unless (looking-at ecasound-prompt-regexp)
                     (error "Assumed ecasound-prompt"))
                   (point))
   comint-last-output-start))

(defun eci-input-filter (string)
  (if (string-match "^[[:space:]]*\\([a-zA-Z-]+\\)[\n\t ]+" string)
      (setq eci-last-command (match-string-no-properties 1 string))))

(defun eci-output-filter (string)
  (let ((start comint-last-input-end)
        (end (process-mark (get-buffer-process (current-buffer)))))
    (goto-char start)
    (if (re-search-forward ecasound-prompt-regexp end t)
      (let ((result (eci-parse start (progn (forward-char -1)
                                            (beginning-of-line)
                                            (point)))))
        (when result
          (setq eci-result
                (cond
                 ((string= eci-last-command "cop-register")
                  (eci-process-cop-register result))
                 ((string= eci-last-command "ladspa-register")
                  (eci-process-ladspa-register result))
                 ((string= eci-last-command "int-output-mode-wellformed")
                  (if (eq result t)
                      (setq eci-int-output-mode-wellformed-flag t)))
                 (t result))))))))

;;; Ecasound-iam-mode pcomplete functions

(defun ecasound-iam-setup-pcomplete ()
  (set (make-local-variable 'pcomplete-command-completion-function)
       (lambda ()
         (pcomplete-here ecasound-iam-commands)))
  (set (make-local-variable 'pcomplete-command-name-function)
       (lambda ()
         (pcomplete-arg 'first)))
  (set (make-local-variable 'pcomplete-parse-arguments-function)
       'ecasound-iam-pcomplete-parse-arguments))

(defun ecasound-iam-pcomplete-parse-arguments ()
  "Parse arguments in the current region. \" :,\" are considered
for splitting."
  (let ((begin (save-excursion (comint-bol nil) (point)))
        (end (point))
        begins args)
    (save-excursion
      (goto-char begin)
      (while (< (point) end)
        (skip-chars-forward " \t\n,:")
        (setq begins (cons (point) begins))
        (let ((skip t))
          (while skip
            (skip-chars-forward "^ \t\n,:")
            (if (eq (char-before) ?\\)
                (skip-chars-forward " \t\n,:")
              (setq skip nil))))
        (setq args (cons (buffer-substring-no-properties
                          (car begins) (point))
                         args)))
      (cons (reverse args) (reverse begins)))))

(defun ecasound-input-file-or-device ()
  "Return a list of possible completions for input device name."
  (append (delq
           nil
           (mapcar
            (lambda (elt)
              (when (string-match
                     (concat "^" (regexp-quote pcomplete-stub)) elt)
                elt))
            (list "alsa" "alsahw" "alsalb" "alsaplugin"
                  "arts" "loop" "null" "stdin")))
          (pcomplete-entries)))

;;;; IAM commands

(defun pcomplete/ecasound-iam-mode/cs-add ()
  (message "Adds a new chainsetup with name `name`.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-remove ()
  (message "Removes currently selected chainsetup.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-list ()
  (message "Returns a list of all chainsetups.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-select ()
  (message "Selects chainsetup `name`.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-selected ()
  (message "Returns the name of currently selected chainsetup.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-load ()
  (pcomplete-here (pcomplete-entries)))

(defun pcomplete/ecasound-iam-mode/cs-save-as ()
  (pcomplete-here (pcomplete-entries)))

(defun pcomplete/ecasound-iam-mode/cs-is-valid ()
  (message "Whether currently selected chainsetup is valid (=can be connected)?")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-connect ()
  (message "Connect currently selected chainsetup to engine.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-disconnect ()
  (message "Disconnect currently connected chainsetup.")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-iam-mode/cs-connected ()
  (message "Returns the name of currently connected chainsetup.")
  (throw 'pcompleted t))

(defun eci-register-find-arg (arg register)
  (let (result)
    (while register
      (if (string= (nth 1 (car register)) arg)
          (setq result (nth 2 (car register))
                register nil))
      (setq register (cdr register)))
    result))

(defun pcomplete/ecasound-iam-mode/cs-rewind ()
  (message "Rewinds the current chainsetup position by `time-in-seconds` seconds.")
  (throw 'pcompleted t))
(defalias 'pcomplete/ecasound-iam-mode/rewind 'pcomplete/ecasound-iam-mode/cs-rewind)
(defalias 'pcomplete/ecasound-iam-mode/rw 'pcomplete/ecasound-iam-mode/cs-rewind)

(defun pcomplete/ecasound-iam-mode/ai-add ()
  (pcomplete-here (ecasound-input-file-or-device)))

(defun pcomplete/ecasound-iam-mode/ao-add ()
  (pcomplete-here (ecasound-input-file-or-device)))

(defun pcomplete/ecasound-iam-mode/cop-add ()
  (unless eci-cop-register
    (eci-cop-register))
  (unless eci-ladspa-register
    (eci-ladspa-register))
  (cond
   ((= pcomplete-last 1)
    (pcomplete-here (mapcar (lambda (elt) (nth 1 elt)) eci-cop-register)))
   ((> pcomplete-last 1)
    (if (and (= pcomplete-last 2)
             (string= (pcomplete-arg) "-el"))
            (progn (pcomplete-next-arg)
                   (pcomplete-here (mapcar (lambda (elt) (nth 1 elt))
                                           eci-ladspa-register)))
      (if (and (string= (pcomplete-arg) "-el")
               (> pcomplete-last 2))
              (let* ((args (eci-register-find-arg (pcomplete-arg -1)
                                                  eci-ladspa-register))
                     (arg (nth (- pcomplete-last 3) args)))
                (if arg
                    (message "%s" arg)
                  (message "No help available")))
        (let* ((args (eci-register-find-arg (pcomplete-arg)
                                            eci-cop-register))
               (arg (nth (- pcomplete-last 2) args)))
          (if arg
              (message "%s" arg)
            (message "No help available")))))))
  (throw 'pcompleted t))


;;; ECI --- The Ecasound Control Interface

(defgroup eci nil
  "Ecasound Control Interface."
  :group 'ecasound)

(defcustom eci-program "ecasound"
  "*Program to invoke when doing `eci-init'."
  :group 'eci
  :type '(choice string (cons string string)))

(defcustom eci-arguments '("-c" "-D")
  "*Arguments used by `eci-init'."
  :group 'eci
  :type '(repeat string))

(defcustom eci-buffer-name "*eci-ecasound*"
  "Buffer name to use for ecasound process buffers."
  :group 'eci
  :type 'string)

(defcustom eci-parse-cleanup-buffer t
  "*Indicates if `eci-parse' should cleanup the buffer.
This means the loglevel, msgsize and return type will get removed if
parsed successfully."
  :group 'eci
  :type 'boolean)

(defcustom eci-error-hook nil
  "*Called whenever a ECI error happens."
  :group 'eci
  :type 'hook)

(defcustom eci-message-hook '(eci-print-message)
  "*Hook called whenever a message except loglevel 256 (eci) is received.
Arguments are LOGLEVEL and STRING."
  :group 'eci
  :type 'hook)

(defface eci-error-face '((t (:foreground "White" :background "Red")))
  "Face used to highlight errors."
  :group 'eci)

(defvar eci-hide-output nil
  "If non-nil, `eci-command' will remove the output generated.")

(defvar eci-last-command nil
  "Last command sent to the ecasound process.")
(make-variable-buffer-local 'eci-last-command)

(defvar eci-return-type nil
  "The return type of the last received return value as a string.")
(make-variable-buffer-local 'eci-return-type)

(defvar eci-return-value nil
  "The last received return value as a string.")
(make-variable-buffer-local 'eci-return-value)

(defvar eci-result nil
  "The last received return value as a Lisp Object.")
(make-variable-buffer-local 'eci-result)

(defvar eci-int-output-mode-wellformed-flag nil
  "Indicates if int-output-mode-wellformed was successfully initialized.")
(make-variable-buffer-local 'eci-int-output-mode-wellformed-flag)

(defvar eci-cop-register nil
  "If non-nil, contains the list of registered chainops.
It has the form
 ((NAME PREFIX (ARGNAME ...)) ...)

Use `eci-cop-register' to fill this list with data.")
(make-variable-buffer-local 'eci-cop-register)

(defvar eci-ladspa-register nil
  "If non-nil, contains the list of registered ladspa plugins.
It has the form
 ((FULL-NAME NAME (ARGNAME ...)) ...)

Use `eci-ladspa-register' to fill this list with data.")
(make-variable-buffer-local 'eci-cop-register)

(defun eci-init ()
  "Initialize a programmatic ECI session.
Every call to this function results in a new sub-process being created
according to `eci-program' and `eci-arguments'. Returns the newly
created buffer.
The caller is responsible for terminating the subprocess at some point."
  (save-excursion
    (set-buffer (generate-new-buffer eci-buffer-name))
    (apply 'make-comint-in-buffer
           "eci" (current-buffer)
           eci-program
           nil
           eci-arguments)
    (ecasound-iam-mode)
    (while (accept-process-output (get-buffer-process (current-buffer)) 1))
    (if (string-match "^256 0 -" (eci-command "int-output-mode-wellformed"))
        (setq eci-int-output-mode-wellformed-flag t))
    (current-buffer)))

(defun eci-interactive-startup ()
  "Used to interactively startup a ECI session using `eci-init'.
This will mostly be used for testing sessions and is equivalent
to `ecasound'."
  (interactive)
  (switch-to-buffer (eci-init)))

(defun eci-command (command &optional buffer-or-process)
  "Send a ECI command to a ECI host process.
COMMAND is the string to be sent, without a newline character.
If BUFFER-OR-PROCESS is nil, first look for a ecasound process in the current
buffer, then for a ecasound buffer with the name *ecasound*,
otherwise use the buffer or process supplied.
Return the string we received in reply to the command except
`eci-int-output-mode-wellformed-flag' is set, which means we can parse the
output via `eci-parse' and return a meaningful value."
  (interactive "sECI Command: ")
  (let ((proc (cond ((processp buffer-or-process) buffer-or-process)
                    ((bufferp buffer-or-process) (get-buffer-process buffer-or-process))
                    ((and (eq major-mode 'ecasound-iam-mode)
                          (comint-check-proc (current-buffer)))
                     (get-buffer-process (current-buffer)))
                    ((comint-check-proc "*ecasound*")
                     (get-buffer-process (get-buffer "*ecasound*")))
                    (t (error
                        "Could not determine suitable ecasound process")))))
    (with-current-buffer (process-buffer proc)
      (let ((moving (= (point) (point-max))))
        (goto-char (process-mark proc))
        (insert (format "%s" command))
        (let (comint-eol-on-send)
          (comint-send-input))
        (let ((here (point)) result)
          (when (accept-process-output proc 2)
            (goto-char here)
            (while (not (re-search-forward ecasound-prompt-regexp nil t))
              (accept-process-output proc 0 50)
              (goto-char here))
            (setq result
                  (if eci-int-output-mode-wellformed-flag
                      eci-result
                    ;; Backward compatibility. Just return the string
                    (buffer-substring-no-properties here (save-excursion
                                        ; Strange hack to avoid fields
                                                           (forward-char -1)
                                                           (beginning-of-line)
                                                           (if (not (= here (point)))
                                                               (forward-char -1))
                                                           (point)))))
            (when (and eci-hide-output result)
              (ecasound-delete-last-in-and-output))
            (if moving (goto-char (point-max)))
            result))))))

(defun eci-print-message (level msg)
  "Simple interactive function which prints every message regardless
which logleve."
  (message "%d: %s" level msg))

(defun eci-message (loglevel msg)
  (run-hook-with-args 'eci-message-hook loglevel msg))

(defun eci-parse (start end)
  "Parse output of ECI command in int-output-mode-wellformed mode.
START and END is the region of output received excluding the prompt.
Return an appropriate lisp value if possible."
  (interactive "r")
  (save-excursion
    (let (type value (end (copy-marker end)))
      (goto-char start)
      (while (re-search-forward
              "^\\([0-9]+\\) \\([0-9]+\\)\\( \\(.*\\)\\)?\n\\(\\(.+\n\\)+\\|\n\\)\n\n?"
              end t)
        (let ((loglevel (string-to-number (match-string-no-properties 1)))
              (msgsize (string-to-number (match-string-no-properties 2)))
              (return-type (match-string-no-properties 4))
              (msg (buffer-substring-no-properties
                    (match-beginning 5) (1- (match-end 5)))))
          (unless (= msgsize (length msg))
;; FIXME The regexp is not quite right, it fails on a string with a \n on
; it's own.
; (error "MSGSIZE %d not same as real size %d: %d %s"
; msgsize (length msg) loglevel msg)
            )
          (if (and (= loglevel 256)
                   (string= return-type "e"))
              (add-text-properties
               (match-beginning 5) (1- (match-end 5))
               (list 'face 'eci-error-face)))
          (when eci-parse-cleanup-buffer
            (delete-region (match-beginning 0) (if (= msgsize 0)
                                                   (match-end 0)
                                                 (delete-char -1)
                                                 (match-beginning 5))))
          (if (not (= loglevel 256))
              (eci-message loglevel msg)
            (setq value msg
                  type return-type))))
; (unless (= (point) end)
; (error "Parser out of sync"))
      (when type
        (setq eci-return-value value eci-return-type type)
        (cond
         ((string= type "e")
          (run-hook-with-args 'eci-error-hook value)
          nil)
         ((string= type "f")
          (string-to-number value))
         ((or (string= type "i")
              (string= type "li"))
          (string-to-number value))
         ((string= type "s")
          value)
         ((string= type "S")
          (split-string value ","))
         ((string= type "-")
          t)
         (t (error "Unimplemented return type %s" type)))))))

(defsubst eci-error-p ()
  "Predicate which can be used to check if the last command produced an error."
  (string= eci-return-value "e"))

;;; ECI commands implemented as lisp functions

(defun eci-run (&optional buffer-or-process)
  (interactive)
  (eci-command "run"))

(defun eci-start (&optional buffer-or-process)
  (interactive)
  (eci-command "start"))

(defun eci-cs-add (chainsetup &optional buffer-or-process)
  (interactive "sChainsetup to add: ")
  (eci-command (format "cs-add %s" chainsetup) buffer-or-process))

(defun eci-cs-connect (&optional buffer-or-process)
  (interactive)
  (eci-command "cs-connect" buffer-or-process))

(defun eci-cs-disconnect (&optional buffer-or-process)
  (interactive)
  (eci-command "cs-disconnect" buffer-or-process))

(defun eci-cs-get-length (&optional buffer-or-process)
  (eci-command "cs-get-length" buffer-or-process))
(defalias 'eci-get-length 'eci-cs-get-length)

(defun eci-cs-get-length-samples (&optional buffer-or-process)
  (interactive)
  (eci-command "cs-get-length-samples" buffer-or-process))
(defalias 'eci-get-length-samples 'eci-cs-get-length-samples)

(defun eci-cs-get-position (&optional buffer-or-process)
  (interactive)
  (eci-command "cs-get-position" buffer-or-process))
(defalias 'eci-cs-getpos 'eci-cs-get-position)
(defalias 'eci-getpos 'eci-cs-get-position)
(defalias 'eci-get-position 'eci-cs-get-position)

(defun eci-cs-index-select (index &optional buffer-or-process)
  (interactive "nChainsetup index: ")
  (eci-command (format "cs-index-select %d" index) buffer-or-process))
(defalias 'eci-cs-iselect 'eci-cs-index-select)

(defun eci-cs-is-valid (&optional buffer-or-process)
  (interactive)
  (let ((val (eci-command "cs-is-valid" buffer-or-process)))
    (setq val
          (cond
           ((= val 0)
            nil)
           ((string= val "1")
            t)
           (t (error "Unexpected reply in cs-is-valid"))))
    (if (interactive-p)
        (message (format "Chainsetup is%s valid" (if val "" " not"))))
    val))

(defun eci-cs-list (&optional buffer-or-process)
  (interactive)
  (let ((val (eci-command "cs-list" buffer-or-process)))
    (if (interactive-p)
        (message (concat "Available chainsetups: "
                         (mapconcat #'identity val ", "))))
    val))

(defun eci-cs-load (filename &optional buffer-or-process)
  (interactive "fChainsetup filename: ")
  (eci-command (format "cs-load %s" filename) buffer-or-process))

(defun eci-cs-save (filename &optional buffer-or-process)
  (interactive)
  (eci-command "cs-save" buffer-or-process))

(defun eci-cs-save-as (filename &optional buffer-or-process)
  (interactive "FChainsetup filename: ")
  (eci-command (format "cs-save-as %s" filename) buffer-or-process))

(defun eci-cs-selected (&optional buffer-or-process)
  (interactive)
  (let ((val (eci-command "cs-selected")))
    (if (interactive-p)
        (message (format "Selected chainsetup: %s" val)))
    val))

(defun eci-cs-status (&optional buffer-or-process)
  "Return ChainSetup status as a Lisp object."
  ;; FIXME, only works with stable
  (let ((chains (split-string (eci-command "cs-status" buffer-or-process)
                              "^Chainsetup " ))
        (chainlist))
    (if (string-match "[\\* Controller/Chainsetup status \\*]" (car chains))
        (progn
          (setq chains (cdr chains))
          (mapc
           (lambda (str)
             (if (string-match "(\\([0-9]+\\)) \"\\([^\"\n]+\\)\".*\n\tFilename:\t\t\\([^\n]*\\)\n\tSetup:\t\t\tinputs \\([0-9]+\\) - outputs \\([0-9]+\\) - chains \\([0-9]+\\)\n\tBuffersize:\t\t\\([0-9]+\\)\n\tInternal sample rate:\t\\([0-9]+\\)\n\tDefault sformat:\t\\(.*\\)\n\tFlags:\t\t\t\\(.*\\)\n\tState:[\t ]+\\(.*\\)" str)
                 (setq
                  chainlist
                  (cons
                   (list
                    (string-to-number (match-string 1 str))
                    (match-string-no-properties 2 str)
                    (list 'filename (match-string-no-properties 3 str))
                    (list 'inputs (string-to-number (match-string 4 str)))
                    (list 'outputs (string-to-number (match-string 5 str)))
                    (list 'chains (string-to-number (match-string 6 str)))
                    (list 'buffersize (string-to-number (match-string 7 str)))
                    (list 'srate (string-to-number (match-string 8 str)))
                    (list 'sformat (match-string-no-properties 9 str))
                    (list 'flags (match-string-no-properties 10 str))
                    (list 'state (match-string-no-properties 11 str))) chainlist))))
           chains))
      (error "Unknown return value"))
    chainlist))

(defun eci-c-add (chains &optional buffer-or-process)
  "Adds a set of chains. Added chains are automatically selected.
If argument CHAINS is a list, its elements are concatenated with ','."
  (interactive "sChain(s) to add: ")
  (eci-command (format "c-add %s"
                       (if (stringp chains)
                           chains
                         (mapconcat #'identity chains ",")))
               buffer-or-process))

(defun eci-c-deselect (chains &optional buffer-or-process)
  "Deselects chains."
  (interactive
   (list
    (let ((avail (eci-c-selected))
          (deselect-chains) (cur))
      (while (and avail
                  (not (string=
                        (setq cur (completing-read
                                   "Chain to deselect: "
                                   (mapcar #'list avail)))
                        "")))
        (add-to-list 'deselect-chains cur)
        (setq avail (delete cur avail)))
      deselect-chains)))
  (eci-command (format "c-deselect %s"
                       (if (stringp chains)
                           chains
                         (mapconcat #'identity chains ",")))))

(defun eci-c-list (&optional buffer-or-process)
  (interactive)
  (eci-command "c-list"))

(defun eci-c-select (chains &optional buffer-or-process)
  "Selects chains. Other chains are automatically deselected."
  (interactive
   (list
    (let ((avail (eci-c-list))
          (select-chains) (cur))
      (while (and avail
                  (not (string=
                        (setq cur (completing-read
                                   "Chain: "
                                   (mapcar #'list avail)))
                        "")))
        (add-to-list 'select-chains cur)
        (setq avail (delete cur avail)))
      select-chains)))
  (eci-command (format "c-select %s"
                       (if (stringp chains)
                           chains
                         (mapconcat #'identity chains ",")))))

(defun eci-c-selected (&optional buffer-or-process)
  (interactive)
  (let ((val (eci-command "c-selected" buffer-or-process)))
    (if (interactive-p)
        (if (null val)
            (message "No selected chains")
          (message (concat "Selected chains: "
                           (mapconcat #'identity val ", ")))))
    val))

(defun eci-c-select-all (&optional buffer-or-process)
  "Selects all chains."
  (interactive)
  (eci-command "c-select-all" buffer-or-process))

(defun eci-cs-select (chainsetup &optional buffer-or-process)
  (interactive (list (completing-read "Chainsetup: " (mapcar
                                                      #'list (eci-cs-list)))))
  (eci-command (format "cs-select %s" chainsetup)))

(defun eci-ai-add (filename)
  (interactive "fInput filename: ")
  (eci-command (format "ai-add %s" filename)))

(defun eci-ao-add (filename)
  (interactive "FOutput filename: ")
  (eci-command (format "ao-add %s" filename)))

(defun eci-engine-status (&optional buffer-or-process)
  (eci-command "engine-status"))

(defun eci-cop-add (string &optional buffer-or-process)
  (interactive "sChainop to add: ")
  (eci-command (format "cop-add %s" string buffer-or-process)))

(defun eci-cop-select (integer &optional buffer-or-process)
  (interactive "nChainop to select: ")
  (eci-command (format "cop-select %d" integer buffer-or-process)))

(defun eci-copp-select (integer &optional buffer-or-process)
  (interactive "nChainop parameter to select: ")
  (eci-command (format "copp-select %d" integer buffer-or-process)))

(defun eci-copp-get (&optional buffer-or-process)
  (eci-command "copp-get" buffer-or-process))

(defun eci-copp-set (value &optional buffer-or-process)
  (interactive "nValue for Chain operator parameter: ")
  (eci-command (format "copp-set %f" value buffer-or-process)))

(defun eci-process-cop-register (string)
  (let (result (cops (split-string string "\n")))
    (while cops
      (if (string-match "^[0-9]+\\. \\([^,]+\\), \\(-[a-zA-Z]+\\):\\(.*\\)" (car cops))
          (setq
           result
           (cons
            (list (match-string-no-properties 1 (car cops))
                  (match-string-no-properties 2 (car cops))
                  (split-string (match-string-no-properties 3 (car cops)) ","))
            result)))
      (setq cops (cdr cops)))
    (set (make-local-variable 'eci-cop-register)
         result)))

(defun eci-process-ladspa-register (string)
  (let (result)
    (with-temp-buffer
      (insert string)
    (goto-char (point-min))
    (while (re-search-forward "[0-9]+\\. \\(.*\\)\n\t-el:\\([^,\n]+\\),\\(.*\\)" nil t)
      (let ((full-name (match-string-no-properties 1))
            (name (match-string-no-properties 2))
            (args (split-string (concat "'," (match-string-no-properties 3)
                                        ",'") "','")))
        (setq result (cons (list full-name name args) result)))))
    (set (make-local-variable 'eci-ladspa-register)
         (nreverse result))))

(defun eci-cop-register ()
  (interactive)
  (let ((eci-hide-output (not (interactive-p))))
    (eci-command "cop-register")))

(defun eci-ladspa-register ()
  (interactive)
  (let ((eci-hide-output (not (interactive-p))))
    (eci-command "ladspa-register")))

(defun ecasound-cop-add ()
  "Interactively prompt for the name and argument of a chain operator to add."
  ;; FIXME, ask for ladspa plugins too.
  (interactive)
  (unless eci-cop-register
    (eci-cop-register))
  (let* ((cop (completing-read "Chain operator: " eci-cop-register))
         (args (nth 2 (assoc cop eci-cop-register)))
         (arg (nth 1 (assoc cop eci-cop-register)))
         args2)
    (while args
      (setq args2 (cons (read-from-minibuffer (concat (car args) ": "))
                        args2))
      (setq args (cdr args)))
    (setq args2 (nreverse args2))
    (eci-cop-add (concat arg (mapconcat #'identity args2 ",")))))

;;;; ECI Examples

(defun eci-example ()
  "Implements the example given in the ECI documentation."
  (interactive)
  (save-current-buffer
    (set-buffer (eci-init))
    (display-buffer (current-buffer))
    (eci-cs-add "play_chainsetup")
    (eci-c-add "1st_chain")
    (call-interactively #'eci-ai-add)
    (eci-ao-add "/dev/dsp")
    (eci-cop-add "-efl:100")
    (eci-cop-select 1) (eci-copp-select 1)
    (eci-cs-connect)
    (eci-command "start")
    (sit-for 1)
    (while (and (string= (eci-engine-status) "running")
                (< (eci-get-position) 15))
      (eci-copp-set (+ (eci-copp-get) 500))
      (sit-for 1))
    (eci-command "stop")
    (eci-cs-disconnect)
    (message (concat "Chain operator status: "
                      (eci-command "cop-status")))))

(defun eci-make-temp-file-name (suffix)
  (concat (make-temp-name
           (expand-file-name "emacs-eci" temporary-file-directory))
          suffix))

(defun ecasound-normalize (filename)
  "Normalize a audio file using ECI."
  (interactive "fFile to normalize: ")
  (let ((tmpfile (eci-make-temp-file-name ".wav")))
    (unwind-protect
        (with-current-buffer (eci-init)
          (display-buffer (current-buffer)) (sit-for 1)
          (eci-cs-add "analyze") (eci-c-add "1")
          (eci-ai-add filename) (eci-ao-add tmpfile)
          (eci-cop-add "-ev")
          (message "Analyzing sample data...")
          (eci-cs-connect) (eci-run)
          (eci-cop-select 1) (eci-copp-select 2)
          (let ((gainfactor (eci-copp-get)))
            (eci-cs-disconnect)
            (if (<= gainfactor 1)
                (message "File already normalized!")
              (eci-cs-add "apply") (eci-c-add "1")
              (eci-ai-add tmpfile) (eci-ao-add filename)
              (eci-cop-add "-ea:100")
              (eci-cop-select 1)
              (eci-copp-select 1)
              (eci-copp-set (* gainfactor 100))
              (eci-cs-connect) (eci-run) (eci-cs-disconnect)
              (message "Done"))))
      (if (file-exists-p tmpfile)
          (delete-file tmpfile)))))


;;; Ecasound .ewf major mode

(defgroup ecasound-ewf nil
  "Ecasound .ewf file mode related variables and faces."
  :prefix "ecasound-ewf-"
  :group 'ecasound)

(defcustom ecasound-ewf-output-device "/dev/dsp"
  "*Default output device used for playing .ewf files."
  :group 'ecasound-ewf
  :type 'string)

(defface ecasound-ewf-keyword-face '((t (:foreground "IndianRed")))
  "The face used for highlighting keywords."
  :group 'ecasound-ewf)

(defface ecasound-ewf-time-face '((t (:foreground "Cyan")))
  "The face used for highlighting time information."
  :group 'ecasound-ewf)

(defface ecasound-ewf-file-face '((t (:foreground "Green")))
  "The face used for highlighting the filname."
  :group 'ecasound-ewf)

(defface ecasound-ewf-boolean-face '((t (:foreground "Orange")))
  "The face used for highlighting boolean values."
  :group 'ecasound-ewf)

(defvar ecasound-ewf-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\t" 'pcomplete)
    (define-key map "\C-c\C-p" 'ecasound-ewf-play)
    map)
  "Keymap for `ecasound-ewf-mode'.")

(defvar ecasound-ewf-mode-syntax-table
  (let ((st (make-syntax-table)))
    (modify-syntax-entry ?# "<" st)
    (modify-syntax-entry ?\n ">" st)
    st)
  "Syntax table for `ecasound-ewf-mode'.")

(defvar ecasound-ewf-font-lock-keywords
  '(("^\\s-*\\(source\\)[^=]+=\\s-*\\(.*\\)$"
     (1 'ecasound-ewf-keyword-face)
     (2 'ecasound-ewf-file-face))
    ("^\\s-*\\(offset\\)[^=]+=\\s-*\\([0-9.]+\\)$"
     (1 'ecasound-ewf-keyword-face)
     (2 'ecasound-ewf-time-face))
    ("^\\s-*\\(start-position\\)[^=]+=\\s-*\\([0-9.]+\\)$"
     (1 'ecasound-ewf-keyword-face)
     (2 'ecasound-ewf-time-face))
    ("^\\s-*\\(length\\)[^=]+=\\s-*\\([0-9.]+\\)$"
     (1 'ecasound-ewf-keyword-face)
     (2 'ecasound-ewf-time-face))
    ("^\\s-*\\(looping\\)[^=]+=\\s-*\\(true\\|false\\)$"
     (1 'ecasound-ewf-keyword-face)
     (2 'ecasound-ewf-boolean-face)))
  "Keyword highlighting specification for `ecasound-ewf-mode'.")

;;;###autoload
(define-derived-mode ecasound-ewf-mode fundamental-mode "EWF"
  "A major mode for editing ecasound .ewf files."
  (set (make-local-variable 'comment-start) "# ")
  (set (make-local-variable 'comment-start-skip) "#+\\s-*")
  (set (make-local-variable 'font-lock-defaults)
       '(ecasound-ewf-font-lock-keywords))
  (ecasound-ewf-setup-pcomplete))

;;; .ewf-mode pcomplete support

(defun ecasound-ewf-keyword-completion-function ()
  (pcomplete-here
   (list "source" "offset" "start-position" "length" "looping")))

(defun pcomplete/ecasound-ewf-mode/source ()
  (pcomplete-here (pcomplete-entries)))

(defun pcomplete/ecasound-ewf-mode/offset ()
  (message "insert audio object at offset (seconds) [read,write]")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-ewf-mode/start-position ()
  (message "start offset inside audio object (seconds) [read]")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-ewf-mode/length ()
  (message "how much of audio object data is used (seconds) [read]")
  (throw 'pcompleted t))

(defun pcomplete/ecasound-ewf-mode/looping ()
  (pcomplete-here (list "true" "false")))

(defun ecasound-ewf-parse-arguments ()
  "Parse whitespace separated arguments in the current region."
  (let ((begin (save-excursion (beginning-of-line) (point)))
        (end (point))
        begins args)
    (save-excursion
      (goto-char begin)
      (while (< (point) end)
        (skip-chars-forward " \t\n=")
        (setq begins (cons (point) begins))
        (let ((skip t))
          (while skip
            (skip-chars-forward "^ \t\n=")
            (if (eq (char-before) ?\\)
                (skip-chars-forward " \t\n=")
              (setq skip nil))))
        (setq args (cons (buffer-substring-no-properties
                          (car begins) (point))
                         args)))
      (cons (reverse args) (reverse begins)))))

(defun ecasound-ewf-setup-pcomplete ()
  (set (make-local-variable 'pcomplete-parse-arguments-function)
       'ecasound-ewf-parse-arguments)
  (set (make-local-variable 'pcomplete-command-completion-function)
       'ecasound-ewf-keyword-completion-function)
  (set (make-local-variable 'pcomplete-command-name-function)
       (lambda ()
         (pcomplete-arg 'first)))
  (set (make-local-variable 'pcomplete-arg-quote-list)
       (list ? )))

;;; Interactive commands

;; FIXME: Make it use ECI.
(defun ecasound-ewf-play ()
  (interactive)
  (let ((ecasound-arguments (list "-c"
                                  "-i" buffer-file-name
                                  "-o" ecasound-ewf-output-device)))
    (and (buffer-modified-p)
         (y-or-n-p "Save file before playing? ")
         (save-buffer))
    (ecasound "*Ecasound-ewf Player*")))

(add-to-list 'auto-mode-alist (cons "\\.ewf$" 'ecasound-ewf-mode))

(provide 'ecasound)
;;; ecasound.el ends here

;; Local variables:
;; mode: outline-minor
;; outline-regexp: ";;;;* \\| "
;; End:

-- 
CYa,
  Mario


New Message Reply About this list Date view Thread view Subject view Author view Other groups

This archive was generated by hypermail 2b28 : Fri Oct 11 2002 - 01:44:34 EEST