Embedding CSound in Common Lisp

314 views Asked by At

I am working on embedding CSound in Lisp. CSound is a music synthesis (and more) open source software.

It has a fairly simple (scripting) language. Quick start (10 min read) is available at the link above. Currently I am working on just the assignment part (which is a large part of the csound language).

Here's my code:

(defparameter *assign-statements* nil)

(defmacro assign (_name value &optional (rate 'i))
  (let* ((name (if (typep _name 'symbol) _name (eval _name)))
         (var (symb (format nil "~(~a~)" rate) name)))
    `(progn 
       (defparameter ,name ',var)
       (defparameter ,var ,value)
       (setf *assign-statements* 
                 (cons (format nil "~A = ~A" ,name ,value) *assign-statements*)))))

(defmacro assign* (&rest args)
  `(progn ,@(mapcar (lambda (arg) (cons 'assign arg)) args)))

(defun opcode-call (opcode &rest args)
  (format nil "~a ~{~a~^, ~}" opcode (mapcar (lambda (arg) 
             (if (stringp arg) 
                 (let ((var (gensym)))
                   (eval (list 'assign (symb (symbol-name var)) arg 'a))
                   (symbol-value (symb (symbol-name var)))) 
                 arg)) 
          args)))

(defmacro op (opcode &rest args)
  `(opcode-call ',opcode ,@args))

To demonstrate what the code does:

(progn  
  (defparameter *assign-statements* nil)
  (assign* 
    (freq 'p4)
    (amp 'p5)
    (att (+ 0.1 0.1))
    (dec 0.4)
    (sus 0.6)
    (rel 0.7)
    (cutoff 5000)
    (res 0.4 k)
    (env (op madsr (op moogladder freq amp) att dec sus rel) k))
  (format t "~{~A~^~%~}~%" 
      (nreverse *assign-statements*)))

outputs:

iFREQ = P4
iAMP = P5
iATT = 0.2
iDEC = 0.4
iSUS = 0.6
iREL = 0.7
iCUTOFF = 5000
kRES = 0.4
aG8707 = MOOGLADDER iFREQ, iAMP
aG8708 = MOOGLADDER iFREQ, iAMP
kENV = MADSR aG8708, iATT, iDEC, iSUS, iREL
NIL  

This is correct in all respects except, "MOOGLADDER iFREQ, iAMP" appears twice.

Why is this? I can't figure out where it's being evaluated twice. How do I eliminate this repetition?


Notes about the code:

  • Csound has the concept of a, k and i rate variables. This is strangely implemented as a prefix to the variable symbol. The closest counterpart in lisp would be that of a global variable. So I've implemented it as such. However to accommodate the rate, I have one level of indirection between the symbol and its value. e.g. the symbol 'res has for its value 'kRes. Now the symbol 'kRes has for its value, the original 0.4.

  • The macros 'op and 'assign* are simple wrappers around 'opcode-call and 'assign respectively.

  • 'opcode-call is a function, and thus automatically allows for normal order evaluation, thus allowing for nested function calls, which csound doesn't (fully) support natively. To get around this, 'opcode-call looks for any evaluated opcode-calls in it's param list by checking it's type (string). If it finds a string, it replaces that with a gensym variable.

  • Each assign call adds the assignment to the list of assign statements, which is then finally used to output to csound language.

1

There are 1 answers

1
Rainer Joswig On BEST ANSWER

Your macro ASSIGN lets the value be computed twice. See the comment below.

(defmacro assign (_name value &optional (rate 'i))
  (let* ((name (if (typep _name 'symbol) _name (eval _name)))
         (var (symb (format nil "~(~a~)" rate) name)))
    `(progn 
       (defparameter ,name ',var)
       (defparameter ,var ,value)
       (push (format nil "~A = ~A" ,name ,var)    ; <- use the var
             *assign-statements*))))

Try it:

CL-USER 52 > (progn  
               (defparameter *assign-statements* nil)
               (assign* 
                (freq 'p4)
                (amp 'p5)
                (att (+ 0.1 0.1))
                (dec 0.4)
                (sus 0.6)
                (rel 0.7)
                (cutoff 5000)
                (res 0.4 k)
                (env (op madsr (op moogladder freq amp) att dec sus rel) k))
               (format t "~{~A~^~%~}~%" 
                       (nreverse *assign-statements*)))
iFREQ = P4
iAMP = P5
iATT = 0.2
iDEC = 0.4
iSUS = 0.6
iREL = 0.7
iCUTOFF = 5000
kRES = 0.4
aG2719 = MOOGLADDER iFREQ, iAMP
kENV = MADSR aG2719, iATT, iDEC, iSUS, iREL
NIL