ecasound.el --- The whole thing version 0.5

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

Subject: ecasound.el --- The whole thing version 0.5
From: Mario Lang (mlang_AT_delysid.org)
Date: Thu Oct 10 2002 - 01:06:57 EEST


Hello.

A new day, a new Emacs-Lisp mile-stone. :) I've collapsed all my ecasound
related elisp code into one file now, because it is actually not that large.

QUICKSTART:
Load this file (eval-buffer or load-library) and set ecasound-program
to the path where your int-output-mode-wellformed capable ecasound binary is.

  (setq ecasound-path "...")

Then do M-x ecasound RET

To see the greatest improvment, try the Menu -> ChainOp -> Add...
or do M-x ecasound-cop-add RET.

To see examples of the ECI implementation in action, use M-x eci-example RET
to get the example from the ecidoc.latex file or have a
look at ecasound-normalize, which is a elisp implementation of
the python example.

(note, the eci library stuff uses a different executable path right now, see
eci-program, you will also want to set this. In the future, we will
use the ECASOUND env.var.)

ecasound-cop-add is really the feature I always wanted and needed,
lets see where we can go from here.
(<wink> I'll be more productive if I get feedback :-) )

;;; 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.5

;; 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.
;;
;; NEWS:
;;
;; Since the last public release:
;;
;; * 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:
;;
;; * Write eci-cop-register alike support for preset-register and
;; ladspa-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))
  (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 (string-match "^256 0 -" (eci-command "int-output-mode-wellformed"))
              (setq eci-int-output-mode-wellformed-flag t)))
        (pop-to-buffer ecasound-buffer))
    (pop-to-buffer buffer)))

;;; 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 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)))


;;; 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-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-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)

(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)
      (goto-char (process-mark proc))
      (insert (format "%s" command))
      (comint-send-input)
      (let ((here (point)))
        (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))
          (if eci-int-output-mode-wellformed-flag
              (eci-parse here (save-excursion
                                (forward-char -1) (beginning-of-line)
                                (point)))
            ;; 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)))))))))

(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-cop-register ()
  (let (result (cops (split-string (eci-command "cop-register") "\n")))
    (while cops
      (if (string-match "^[0-9]+\\. \\([^,]+\\), \\(-[a-zA-Z]+:\\(.*\\)\\)" (car cops))
          (setq
           result
           (cons
            (list (match-string-no-properties 1 (car cops))
                  (substring (car cops) (match-beginning 2)
                                        (match-beginning 3))
                  (split-string (match-string-no-properties 3 (car cops)) ","))
            result)))
      (setq cops (cdr cops)))
    (set (make-local-variable 'eci-cop-register)
         result)))

(defun ecasound-cop-add ()
  "Interactively prompt for the name and argument of a chain operator to add."
  (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 : Thu Oct 10 2002 - 01:05:18 EEST