mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-04 08:04:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			160 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
;; seqmidi.lsp -- functions to use MIDI files in Nyquist
 | 
						|
;
 | 
						|
; example call:
 | 
						|
; 
 | 
						|
; (seq-midi my-seq
 | 
						|
;   (note (chan pitch velocity) (= chan 2) (my-note pitch velocity))
 | 
						|
;   (ctrl (chan control value) (...))
 | 
						|
;   (bend (chan value) (...))
 | 
						|
;   (touch (chan value) (...))
 | 
						|
;   (prgm (chan value) (setf (aref my-prgm chan) value))
 | 
						|
 | 
						|
;; seq-midi - a macro to create a sequence of sounds based on midi file
 | 
						|
;
 | 
						|
; 
 | 
						|
(defmacro seq-midi (the-seq &rest cases)
 | 
						|
  (seq-midi-cases-syntax-check cases)
 | 
						|
  `(let (_the-event _next-time _the-seq _seq-midi-closure _nyq-environment 
 | 
						|
         _the-seq _tag)
 | 
						|
    (setf _the-seq (seq-copy ,the-seq))
 | 
						|
    (setf _nyq-environment (nyq:the-environment))
 | 
						|
    (setf _seq-midi-closure #'(lambda (t0)
 | 
						|
      ; (format t "_seq_midi_closure: t0 = ~A~%" t0)
 | 
						|
      (prog (_the-sound)
 | 
						|
loop	; go forward until we find note to play (we may be there)
 | 
						|
        ; then go forward to find time of next note
 | 
						|
        (setf _the-event (seq-get _the-seq))
 | 
						|
        ; (display "seq-midi" _the-event t0)
 | 
						|
        (setf _tag (seq-tag _the-event))
 | 
						|
        (cond ((= _tag seq-ctrl-tag)
 | 
						|
               ,(make-ctrl-handler cases))
 | 
						|
              ((= _tag seq-bend-tag)
 | 
						|
               ,(make-bend-handler cases))
 | 
						|
              ((= _tag seq-touch-tag)
 | 
						|
               ,(make-touch-handler cases))
 | 
						|
              ((= _tag seq-prgm-tag)
 | 
						|
               ,(make-prgm-handler cases))
 | 
						|
              ((= _tag seq-done-tag)
 | 
						|
               ; (format t "_seq_midi_closure: seq-done")
 | 
						|
               (cond (_the-sound ; this is the last sound of sequence
 | 
						|
                      ; (format t "returning _the-sound~%")
 | 
						|
                      (return _the-sound))
 | 
						|
                     (t ; sequence is empty, return silence
 | 
						|
                      ; (format t "returning snd-zero~%")
 | 
						|
                      (return (snd-zero t0 *sound-srate*)))))
 | 
						|
              ((and (= _tag seq-note-tag)
 | 
						|
                    ,(make-note-test cases))
 | 
						|
               (cond (_the-sound ; we now have time of next note
 | 
						|
                      (setf _next-time (/ (seq-time _the-event) 1000.0))
 | 
						|
                      (go exit-loop))
 | 
						|
                     (t
 | 
						|
                      (setf _the-sound ,(make-note-handler cases))))))
 | 
						|
        (seq-next _the-seq)
 | 
						|
        (go loop)
 | 
						|
exit-loop ; here, we know time of next note
 | 
						|
        ; (display "seq-midi" _next-time)
 | 
						|
        ; (format t "seq-midi calling snd-seq\n")
 | 
						|
        (return (snd-seq
 | 
						|
                  (set-logical-stop-abs _the-sound 
 | 
						|
                        (local-to-global _next-time))
 | 
						|
                  _seq-midi-closure)))))
 | 
						|
    ; (display "calling closure" (get-lambda-expression _seq-midi-closure))
 | 
						|
    (funcall _seq-midi-closure (local-to-global 0))))
 | 
						|
 | 
						|
 | 
						|
(defun seq-midi-cases-syntax-check (cases &aux n)
 | 
						|
  (cond ((not (listp cases))
 | 
						|
         (break "syntax error in" cases)))
 | 
						|
  (dolist (case cases)
 | 
						|
    (cond ((or (not (listp case)) 
 | 
						|
               (not (member (car case) '(NOTE CTRL BEND TOUCH PRGM)))
 | 
						|
               (not (listp (cdr case)))
 | 
						|
               (not (listp (cadr case)))
 | 
						|
               (not (listp (cddr case)))
 | 
						|
               (not (listp (last (cddr case)))))
 | 
						|
           (break "syntax error in" case))
 | 
						|
          ((/= (length (cadr case))
 | 
						|
               (setf n (cdr (assoc (car case) 
 | 
						|
                             '((NOTE . 3) (CTRL . 3) (BEND . 2)
 | 
						|
                               (TOUCH . 2) (PRGM . 2))))))
 | 
						|
           (break (format nil "expecting ~A arguments in" n) case))
 | 
						|
          ((and (eq (car case) 'NOTE)
 | 
						|
                (not (member (length (cddr case)) '(1 2))))
 | 
						|
           (break 
 | 
						|
            "note handler syntax is (NOTE (ch pitch vel) [filter] behavior)"
 | 
						|
            case)))))
 | 
						|
 | 
						|
 | 
						|
(defun make-ctrl-handler (cases)
 | 
						|
  (let ((case (assoc 'ctrl cases)))
 | 
						|
    (cond (case
 | 
						|
           `(let ((,(caadr case) (seq-channel _the-event))
 | 
						|
                  (,(cadadr case) (seq-control _the-event))
 | 
						|
                  (,(caddar (cdr case)) (seq-value _the-event)))
 | 
						|
              ,@(cddr case)))
 | 
						|
          (t nil))))
 | 
						|
 | 
						|
(defun make-bend-handler (cases)
 | 
						|
  (let ((case (assoc 'bend cases)))
 | 
						|
    (cond (case
 | 
						|
           `(let ((,(caadr case) (seq-channel _the-event))
 | 
						|
                  (,(cadadr case) (seq-value _the-event)))
 | 
						|
              ,@(cddr case)))
 | 
						|
          (t nil))))
 | 
						|
 | 
						|
(defun make-touch-handler (cases)
 | 
						|
  (let ((case (assoc 'touch cases)))
 | 
						|
    (cond (case
 | 
						|
           `(let ((,(caadr case) (seq-channel _the-event))
 | 
						|
                  (,(cadadr case) (seq-value _the-event)))
 | 
						|
              ,@(cddr case)))
 | 
						|
          (t nil))))
 | 
						|
 | 
						|
(defun make-prgm-handler (cases)
 | 
						|
  (let ((case (assoc 'pgrm cases)))
 | 
						|
    (cond (case
 | 
						|
           `(let ((,(caadr case) (seq-channel _the-event))
 | 
						|
                  (,(cadadr case) (seq-value _the-event)))
 | 
						|
              ,@(cddr case)))
 | 
						|
          (t nil))))
 | 
						|
 | 
						|
(defun make-note-test (cases)
 | 
						|
  (let ((case (assoc 'note cases)))
 | 
						|
    (cond ((and case (cdddr case))
 | 
						|
           (caddr case))
 | 
						|
          (t t))))
 | 
						|
           
 | 
						|
 | 
						|
(defun make-note-handler (cases)
 | 
						|
  (let ((case (assoc 'note cases))
 | 
						|
        behavior)
 | 
						|
    (cond ((and case (cdddr case))
 | 
						|
           (setf behavior (cadddr case)))
 | 
						|
          (t
 | 
						|
           (setf behavior (caddr case))))
 | 
						|
    `(with%environment _nyq-environment
 | 
						|
        (with-note-args ,(cadr case) _the-event ,behavior))))
 | 
						|
 | 
						|
 | 
						|
(defmacro with-note-args (note-args the-event note-behavior)
 | 
						|
  ; (display "with-note-args" the-event)
 | 
						|
  `(let ((,(car note-args) (seq-channel ,the-event))
 | 
						|
         (,(cadr note-args) (seq-pitch ,the-event))
 | 
						|
         (,(caddr note-args) (seq-velocity ,the-event)))
 | 
						|
     (at (/ (seq-time ,the-event) 1000.0)
 | 
						|
      (stretch (/ (seq-duration ,the-event) 1000.0) ,note-behavior))))
 | 
						|
 | 
						|
 | 
						|
;(defun seq-next-note-time (the-seq find-first-flag)
 | 
						|
;  (prog (event)
 | 
						|
;    (if find-first-flag nil (seq-next the-seq))
 | 
						|
;loop
 | 
						|
;    (setf event (seq-get the-seq))
 | 
						|
;    (cond ((eq (seq-tag event) seq-done-tag)
 | 
						|
;	   (return (if find-first-flag 0.0 nil)))
 | 
						|
;	  ((eq (seq-tag event) seq-note-tag)
 | 
						|
;	   (return (/ (seq-time event) 1000.0))))
 | 
						|
;    (seq-next the-seq)
 | 
						|
;    (go loop)))
 | 
						|
; 
 |