mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-04 08:04:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2333 lines
		
	
	
		
			77 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			2333 lines
		
	
	
		
			77 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
;; X-Music, inspired by Commmon Music
 | 
						|
 | 
						|
#|
 | 
						|
PATTERN SEMANTICS
 | 
						|
 | 
						|
Patterns are objects that are generally accessed by calling 
 | 
						|
(next pattern). Each call returns the next item in an 
 | 
						|
infinite sequence generated by the pattern. Items are 
 | 
						|
organized into periods. You can access all (remaining) 
 | 
						|
items in the current period using (next pattern t).
 | 
						|
 | 
						|
Patterns mark the end-of-period with +eop+, a distinguished
 | 
						|
atom. The +eop+ markers are filtered out by the next function
 | 
						|
but returned by the :next method.
 | 
						|
 | 
						|
Pattern items may be patterns. This is called a nested
 | 
						|
pattern.  When patterns are nested, you return a period 
 | 
						|
from the innermost pattern, i.e. traversal is depth-first. 
 | 
						|
This means when you are using something like random, you
 | 
						|
have to remember the last thing returned and keep getting
 | 
						|
the next element from that thing until you see +eop+; 
 | 
						|
then you move on. It's a bit more complicated because 
 | 
						|
a pattern advances when its immediate child pattern
 | 
						|
finishes a cycle, but +eop+ is only returned from the
 | 
						|
"leaf" patterns.
 | 
						|
 | 
						|
With nested patterns, i.e. patterns with items that
 | 
						|
are patterns, the implementation requires that
 | 
						|
*all* items must be patterns. The application does
 | 
						|
*not* have to make every item a pattern, so the
 | 
						|
implementation "cleans up" the item list: Any item
 | 
						|
that is not a pattern is be replaced with a cycle
 | 
						|
pattern whose list contains just the one item.
 | 
						|
 | 
						|
EXPLICIT PATTERN LENGTH
 | 
						|
 | 
						|
Pattern length may be given explicitly by a number or
 | 
						|
a pattern that generates numbers. Generally this is 
 | 
						|
specified as the optional :for keyword parameter when
 | 
						|
the pattern is created. If the explicit pattern
 | 
						|
length is a number, this will be the period length,
 | 
						|
overriding all implicit lengths. If the pattern length
 | 
						|
is itself a pattern, the pattern is evaluated every 
 | 
						|
period to determine the length of the next period,
 | 
						|
overriding any implicit length. 
 | 
						|
 | 
						|
IMPLEMENTATION
 | 
						|
 | 
						|
There are 3 ways to determine lengths: 
 | 
						|
1) The length is implicit. The length can be
 | 
						|
computed (at some point) and turned into an
 | 
						|
explicit length.
 | 
						|
 | 
						|
2) The length is explicit. This overrides the
 | 
						|
implicit length. The explicit length is stored as
 | 
						|
a counter that tells how many more items to generate
 | 
						|
in the current period.
 | 
						|
 | 
						|
3) The length can be generated by a pattern.
 | 
						|
The pattern is evaluated to generate an explicit
 | 
						|
length.
 | 
						|
 | 
						|
So ultimately, we just need a mechanism to handle
 | 
						|
explicit lengths. This is incorporated into the
 | 
						|
pattern-class. The pattern-class sends :start-period
 | 
						|
before calling :advance when the first item in a
 | 
						|
period is about to be generated. Also, :next returns
 | 
						|
+eop+ automatically at the end of a period.
 | 
						|
 | 
						|
Because evaluation is "depth first," i.e. we 
 | 
						|
advance to the next top-level item only after a period
 | 
						|
is generated from a lower-level pattern, every pattern
 | 
						|
has a "current" field that holds the current item. the
 | 
						|
"have-current" field is a flag to tell when the "current"
 | 
						|
field is valid. It is initialized to nil.
 | 
						|
 | 
						|
To generate an element, you need to follow the nested
 | 
						|
patterns all the way to the leaf pattern for every 
 | 
						|
generated item. This is perhaps less efficient than
 | 
						|
storing the current leaf pattern at the top level, but
 | 
						|
patterns can be shared, i.e. a pattern can be a 
 | 
						|
sub-pattern of multiple patterns, so current position
 | 
						|
in the tree structure of patterns can change at 
 | 
						|
any time.
 | 
						|
 | 
						|
The evaluation of nested patterns is depth-first
 | 
						|
and the next shallower level advances when its current
 | 
						|
child pattern completes a cycle. To facilitate this
 | 
						|
step, the :advance method, which advances a pattern
 | 
						|
and computes "current", returns +eonp+, which is a
 | 
						|
marker that a nested pattern has completed a cycle.
 | 
						|
 | 
						|
The :next method generates the next item or +eop+ from
 | 
						|
a pattern. The algorithm in psuedo-code is roughly this:
 | 
						|
 | 
						|
next(p)
 | 
						|
    while true:
 | 
						|
        if not have-current
 | 
						|
            pattern-advance()
 | 
						|
            have-current = true
 | 
						|
            if is-nested and current = eop:
 | 
						|
                have-current = false
 | 
						|
                return eonp
 | 
						|
        if is-nested:
 | 
						|
            rslt = next(current)
 | 
						|
            if rslt == eonp
 | 
						|
                have-current = false
 | 
						|
            elif rslt == eop and not current.is-nested
 | 
						|
                have-current = false
 | 
						|
                return rslt
 | 
						|
            else
 | 
						|
                return rslt
 | 
						|
        else
 | 
						|
            have-current = nil
 | 
						|
            return current
 | 
						|
 | 
						|
pattern-advance
 | 
						|
    // length-pattern is either a pattern or a constant
 | 
						|
    if null(count) and length-pattern:
 | 
						|
        count = next(length-pattern)
 | 
						|
        start-period() // subclass-specific computation
 | 
						|
    if null(count)
 | 
						|
        error
 | 
						|
    if count == 0
 | 
						|
        current = eop
 | 
						|
        count = nil
 | 
						|
    else
 | 
						|
        advance() // subclass-specific computation
 | 
						|
        count--
 | 
						|
 | 
						|
 | 
						|
SUBCLASS RESPONSIBILITIES
 | 
						|
 | 
						|
Note that :advance is the method to override in the 
 | 
						|
various subclasses of pattern-class. The :advance()
 | 
						|
method computes the next element in the infinite
 | 
						|
sequence of items and puts the item in the "current"
 | 
						|
field. 
 | 
						|
 | 
						|
The :start-period method is called before calling 
 | 
						|
advance to get the first item of a new period.
 | 
						|
 | 
						|
Finally, set the is-nested flag if there are nested patterns,
 | 
						|
and make all items of any nested pattern be patterns (no
 | 
						|
mix of patterns and non-patterns is allowed; use 
 | 
						|
    (MAKE-CYCLE (LIST item))
 | 
						|
to convert a non-pattern to a pattern).
 | 
						|
 | 
						|
|#
 | 
						|
 | 
						|
(setf SCORE-EPSILON 0.000001)
 | 
						|
 | 
						|
(setf pattern-class 
 | 
						|
  (send class :new '(current have-current is-nested name count
 | 
						|
                     length-pattern trace)))
 | 
						|
 | 
						|
(defun patternp (x) 
 | 
						|
  (and (objectp x) (send x :isa pattern-class)))
 | 
						|
 | 
						|
(setf +eop+ '+eop+)
 | 
						|
(setf +eonp+ '+eonp+) ;; end of nested period, this indicates you
 | 
						|
   ;; should advance yourself and call back to get the next element
 | 
						|
 | 
						|
(defun check-for-list (lis name)
 | 
						|
  (if (not (listp lis))
 | 
						|
      (error (format nil "~A, requires a list of elements" name))))
 | 
						|
 | 
						|
(defun check-for-list-or-pattern (lis name)
 | 
						|
  (if (not (or (listp lis) (patternp lis)))
 | 
						|
      (error (format nil "~A, requires a list of elements or a pattern" name))))
 | 
						|
 | 
						|
(defun list-has-pattern (lis)
 | 
						|
  (dolist (e lis) 
 | 
						|
    (if (patternp e) (return t))))
 | 
						|
 | 
						|
(defun is-homogeneous (lis)
 | 
						|
  (let (type)
 | 
						|
    (dolist (elem lis t)
 | 
						|
      (cond ((null type)
 | 
						|
             (setf type (if (patternp elem) 'pattern 'atom)))
 | 
						|
            ((and (eq type 'pattern)
 | 
						|
                  (not (patternp elem)))
 | 
						|
             (return nil))
 | 
						|
            ((and (eq type 'atom)
 | 
						|
                  (patternp elem))
 | 
						|
             (return nil))))))
 | 
						|
 | 
						|
(defun make-homogeneous (lis)
 | 
						|
  (cond ((is-homogeneous lis) lis)
 | 
						|
        (t
 | 
						|
         (mapcar #'(lambda (item)
 | 
						|
                     (if (patternp item) item 
 | 
						|
                         (make-cycle (list item)
 | 
						|
                          ;; help debugging by naming the new pattern
 | 
						|
                          ;; probably, the name could be item, but
 | 
						|
                          ;; here we coerce item to a string to avoid
 | 
						|
                          ;; surprises in code that assumes string names.
 | 
						|
                          :name (format nil "~A" item))))
 | 
						|
                 lis))))
 | 
						|
 | 
						|
 | 
						|
(send pattern-class :answer :next '()
 | 
						|
  '(;(display ":next" name is-nested)
 | 
						|
    (loop
 | 
						|
     (cond ((not have-current)
 | 
						|
            (send self :pattern-advance)
 | 
						|
            (setf have-current t)
 | 
						|
            (cond (trace
 | 
						|
                   (format t "pattern ~A advanced to ~A~%"
 | 
						|
                           (if name name "<no-name>")
 | 
						|
                           (if (patternp current) 
 | 
						|
                               (if (send current :name)
 | 
						|
                                   (send current :name)
 | 
						|
                                   "<a-pattern>")
 | 
						|
                               current))))
 | 
						|
            (cond ((and is-nested (eq current +eop+))
 | 
						|
                   ;(display ":next returning eonp" name)
 | 
						|
                   (setf have-current nil)
 | 
						|
                   (return +eonp+)))))
 | 
						|
     (cond (is-nested
 | 
						|
            (let ((rslt (send current :next)))
 | 
						|
              (cond ((eq rslt +eonp+)
 | 
						|
                     (setf have-current nil))
 | 
						|
                    ;; advance next-to-leaf level at end of leaf's period
 | 
						|
                    ((and (eq rslt +eop+) (not (send current :is-nested)))
 | 
						|
                     (setf have-current nil)
 | 
						|
                     ;; return +eof+ because it's the end of leaf's period
 | 
						|
                     (return rslt))
 | 
						|
                    (t
 | 
						|
                     (return rslt)))))
 | 
						|
           (t
 | 
						|
            (setf have-current nil)
 | 
						|
            (return current))))))
 | 
						|
 | 
						|
 | 
						|
;; :PATTERN-ADVANCE -- advance to the next item in a pattern
 | 
						|
;; 
 | 
						|
;; this code is used by every class. class-specific behavior
 | 
						|
;; is implemented by :advance, which this method calls
 | 
						|
;;
 | 
						|
(send pattern-class :answer :pattern-advance '()
 | 
						|
  '(;(display "enter :pattern-advance" self name count current is-nested)
 | 
						|
    (cond ((null count)
 | 
						|
           ;(display "in :pattern-advance" name count length-pattern)
 | 
						|
           (if length-pattern
 | 
						|
               (setf count (next length-pattern)))
 | 
						|
           ;; if count is still null, :start-period must set count
 | 
						|
           (send self :start-period)))
 | 
						|
    (cond ((null count)
 | 
						|
           (error
 | 
						|
            (format nil
 | 
						|
             "~A, pattern-class :pattern-advance has null count" name))))
 | 
						|
    (cond ((zerop count)
 | 
						|
           (setf current +eop+)
 | 
						|
           (setf count nil))
 | 
						|
          (t
 | 
						|
           (send self :advance)
 | 
						|
           (decf count)))
 | 
						|
    ;(display "exit :pattern-advance" name count current)
 | 
						|
    ))
 | 
						|
 | 
						|
 | 
						|
(send pattern-class :answer :is-nested '() '(is-nested))
 | 
						|
 | 
						|
 | 
						|
(send pattern-class :answer :name '() '(name))
 | 
						|
 | 
						|
 | 
						|
(send pattern-class :answer :set-current '(c)
 | 
						|
  '((setf current c)
 | 
						|
    (let ((value
 | 
						|
           (if (patternp current) 
 | 
						|
               (send current :name)
 | 
						|
               current)))
 | 
						|
      ;(display ":set-current" name value)
 | 
						|
      )))
 | 
						|
 | 
						|
 | 
						|
;; next -- get the next element in a pattern
 | 
						|
;;
 | 
						|
;; any non-pattern value is simply returned
 | 
						|
;;
 | 
						|
(defun next (pattern &optional period-flag) 
 | 
						|
  ;(display "next" pattern period-flag (patternp pattern))
 | 
						|
  (cond ((and period-flag (patternp pattern))
 | 
						|
         (let (rslt elem)
 | 
						|
           (while (not (eq (setf elem (send pattern :next)) +eop+))
 | 
						|
               ;(display "next t" (send pattern :name) elem)
 | 
						|
               (if (not (eq elem +eonp+)) 
 | 
						|
                   (push elem rslt)))
 | 
						|
           (reverse rslt)))
 | 
						|
        (period-flag
 | 
						|
         (display "next" pattern)
 | 
						|
         (error (format nil "~A, next expected a pattern"
 | 
						|
                            (send pattern :name))))
 | 
						|
        ((patternp pattern)
 | 
						|
         ;(display "next" (send pattern :name) pattern)
 | 
						|
         (let (rslt)
 | 
						|
           (dotimes (i 10000 (error
 | 
						|
                 (format nil
 | 
						|
                  "~A, just retrieved 10000 empty periods -- is there a bug?"
 | 
						|
                  (send pattern :name))))
 | 
						|
             (if (not (member (setf rslt (send pattern :next)) 
 | 
						|
                              '(+eop+ +eonp+)))
 | 
						|
                 (return rslt)))))
 | 
						|
        (t ;; pattern not a pattern, so just return it:
 | 
						|
         ;(display "next" pattern)
 | 
						|
         pattern)))
 | 
						|
 | 
						|
;; ---- LENGTH Class ----
 | 
						|
 | 
						|
(setf length-class 
 | 
						|
  (send class :new '(pattern length-pattern) '() pattern-class))
 | 
						|
 | 
						|
(send length-class :answer :isnew '(p l nm tr)
 | 
						|
  '((setf pattern p length-pattern l name nm trace tr)))
 | 
						|
 | 
						|
;; note that count is used as a flag as well as a counter.
 | 
						|
;; If count is nil, then the pattern-length has not been
 | 
						|
;; determined. Count is nil intitially and again at the 
 | 
						|
;; end of each period. Otherwise, count is an integer
 | 
						|
;; used to count down the number of items remaining in 
 | 
						|
;; the period.
 | 
						|
 | 
						|
(send length-class :answer :start-period '()
 | 
						|
  '((setf count (next length-pattern))))
 | 
						|
 | 
						|
(send length-class :answer :advance '()
 | 
						|
  '((send self :set-current (next pattern))))
 | 
						|
 | 
						|
(defun make-length (pattern length-pattern &key (name "length") trace)
 | 
						|
  (send length-class :new pattern length-pattern name trace))
 | 
						|
 | 
						|
;; ---- CYCLE Class ---------
 | 
						|
 | 
						|
(setf cycle-class (send class :new 
 | 
						|
                        '(lis cursor lis-pattern)
 | 
						|
                        '() pattern-class))
 | 
						|
 | 
						|
(send cycle-class :answer :isnew '(l for nm tr)
 | 
						|
  '((cond ((patternp l)
 | 
						|
           (setf lis-pattern l))
 | 
						|
          ((listp l)
 | 
						|
           (send self :set-list l))
 | 
						|
          (t
 | 
						|
           (error (format nil "~A, expected list" nm) l)))
 | 
						|
    (setf length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
(send cycle-class :answer :set-list '(l)
 | 
						|
  '((setf lis l)
 | 
						|
    (check-for-list lis "cycle-class :set-list")
 | 
						|
    (setf is-nested (list-has-pattern lis))
 | 
						|
    (setf lis (make-homogeneous lis))))
 | 
						|
 | 
						|
 | 
						|
(send cycle-class :answer :start-period '()
 | 
						|
  '(;(display "cycle-class :start-period" lis-pattern lis count length-pattern)
 | 
						|
    (cond (lis-pattern
 | 
						|
           (send self :set-list (next lis-pattern t))
 | 
						|
           (setf cursor lis)))
 | 
						|
    (if (null count)
 | 
						|
        (setf count (length lis)))))
 | 
						|
  
 | 
						|
 | 
						|
(send cycle-class :answer :advance '()
 | 
						|
  '((cond ((and (null cursor) lis)
 | 
						|
           (setf cursor lis))
 | 
						|
          ((null cursor)
 | 
						|
           (error (format nil "~A, :advance - no items" name))))
 | 
						|
    (send self :set-current (car cursor))
 | 
						|
    (pop cursor)))
 | 
						|
 | 
						|
 | 
						|
(defun make-cycle (lis &key for (name "cycle") trace)
 | 
						|
   (check-for-list-or-pattern lis "make-cycle")
 | 
						|
   (send cycle-class :new lis for name trace))
 | 
						|
 | 
						|
;; ---- LINE class ----
 | 
						|
 | 
						|
(setf line-class (send class :new '(lis cursor lis-pattern) 
 | 
						|
                       '() pattern-class))
 | 
						|
 | 
						|
(send line-class :answer :isnew '(l for nm tr)
 | 
						|
  '((cond ((patternp l)
 | 
						|
           (setf lis-pattern l))
 | 
						|
          ((listp l)
 | 
						|
           (send self :set-list l))
 | 
						|
          (t
 | 
						|
           (error (format nil "~A, expected list" nm) l)))
 | 
						|
    (setf length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
(send line-class :answer :set-list '(l)
 | 
						|
  '((setf lis l)
 | 
						|
    (check-for-list lis "line-class :set-list")
 | 
						|
    (setf is-nested (list-has-pattern lis))
 | 
						|
    (setf lis (make-homogeneous l))
 | 
						|
    (setf cursor lis)))
 | 
						|
 | 
						|
 | 
						|
(send line-class :answer :start-period '()
 | 
						|
  '((cond (lis-pattern
 | 
						|
           (send self :set-list (next lis-pattern t))
 | 
						|
           (setf cursor lis)))
 | 
						|
    (if (null count)
 | 
						|
        (setf count (length lis)))))
 | 
						|
 | 
						|
 | 
						|
(send line-class :answer :advance '()
 | 
						|
  '((cond ((null cursor)
 | 
						|
           (error (format nil "~A, :advance - no items" name))))
 | 
						|
    (send self :set-current (car cursor))
 | 
						|
    (if (cdr cursor) (pop cursor))))
 | 
						|
  
 | 
						|
 | 
						|
(defun make-line (lis &key for (name "line") trace)
 | 
						|
   (check-for-list-or-pattern lis "make-line")
 | 
						|
   (send line-class :new lis for name trace))
 | 
						|
 | 
						|
 | 
						|
;; ---- RANDOM class -----
 | 
						|
 | 
						|
(setf random-class (send class :new 
 | 
						|
       '(lis lis-pattern len previous repeats mincnt maxcnt) 
 | 
						|
       '() pattern-class))
 | 
						|
 | 
						|
;; the structure is (value weight weight-pattern max min)
 | 
						|
(setfn rand-item-value car)
 | 
						|
(defun set-rand-item-value (item value) (setf (car item) value))
 | 
						|
(setfn rand-item-weight cadr)
 | 
						|
(defun set-rand-item-weight (item weight) (setf (car (cdr item)) weight))
 | 
						|
(setfn rand-item-weight-pattern caddr)
 | 
						|
(setfn rand-item-max cadddr)
 | 
						|
(defun rand-item-min (lis) (car (cddddr lis)))
 | 
						|
 | 
						|
 | 
						|
(defun select-random (len lis previous repeats mincnt maxcnt)
 | 
						|
  (let (sum items r)
 | 
						|
    (cond ((zerop len)
 | 
						|
           (break "random-class has no list to choose from")
 | 
						|
           nil)
 | 
						|
          (t
 | 
						|
           (setf sum 0)
 | 
						|
           (dolist (item lis)
 | 
						|
             (setf sum (+ sum (rand-item-weight item))))
 | 
						|
           (setf items lis)
 | 
						|
           (setf r (rrandom))
 | 
						|
           (setf sum (* sum r))
 | 
						|
           (setf rbd-count-all (incf rbd-count-all))
 | 
						|
           (loop
 | 
						|
             (setf sum (- sum (rand-item-weight (car items))))
 | 
						|
             (if (<= sum 0) (return (car items)))
 | 
						|
             (setf rbd-count-two (incf rbd-count-two))
 | 
						|
             (setf items (cdr items)))))))
 | 
						|
 | 
						|
 | 
						|
(defun random-convert-spec (item)
 | 
						|
  ;; convert (value :weight wp :min min :max max) to (value nil wp max min)
 | 
						|
  (let (value (wp 1) mincnt maxcnt lis)
 | 
						|
    (setf value (car item))
 | 
						|
    (setf lis (cdr item))
 | 
						|
    (while lis
 | 
						|
      (cond ((eq (car lis) :weight)
 | 
						|
             (setf wp (cadr lis)))
 | 
						|
            ((eq (car lis) :min)
 | 
						|
             (setf mincnt (cadr lis)))
 | 
						|
            ((eq (car lis) :max)
 | 
						|
             (setf maxcnt (cadr lis)))
 | 
						|
            (t
 | 
						|
             (error "(make-random) item syntax error" item)))
 | 
						|
      (setf lis (cddr lis)))
 | 
						|
    (list value nil wp maxcnt mincnt)))
 | 
						|
 | 
						|
 | 
						|
(defun random-atom-to-list (a)
 | 
						|
  (if (atom a)
 | 
						|
      (list a nil 1 nil nil)
 | 
						|
      (random-convert-spec a)))
 | 
						|
 | 
						|
 | 
						|
(send random-class :answer :isnew '(l for nm tr)
 | 
						|
  ;; there are two things we have to normalize:
 | 
						|
  ;; (1) make all items lists
 | 
						|
  ;; (2) if any item is a pattern, make all items patterns
 | 
						|
  '((cond ((patternp l)
 | 
						|
           (setf lis-pattern l))
 | 
						|
          ((listp l)
 | 
						|
           (send self :set-list l))
 | 
						|
          (t
 | 
						|
           (error (format nil "~A, expected list") l)))
 | 
						|
    (setf rbd-count-all 0 rbd-count-two 0)
 | 
						|
    (setf length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
(send random-class :answer :set-list '(l)
 | 
						|
  '((check-for-list l "random-class :set-list")
 | 
						|
    (setf lis (mapcar #'random-atom-to-list l))
 | 
						|
    (dolist (item lis)
 | 
						|
      (if (patternp (rand-item-value item))
 | 
						|
          (setf is-nested t)))
 | 
						|
    (if is-nested
 | 
						|
        (mapcar #'(lambda (item)
 | 
						|
                    (if (not (patternp (rand-item-value item)))
 | 
						|
                        (set-rand-item-value item 
 | 
						|
                         (make-cycle (list (rand-item-value item))))))
 | 
						|
                lis))
 | 
						|
    ;(display "random is-new" lis)
 | 
						|
    (setf repeats 0)
 | 
						|
    (setf len (length lis))))
 | 
						|
 | 
						|
    
 | 
						|
(send random-class :answer :start-period '()
 | 
						|
  '(;(display "random-class :start-period" count len lis lis-pattern)
 | 
						|
    (cond (lis-pattern
 | 
						|
           (send self :set-list (next lis-pattern t))))
 | 
						|
    (if (null count)
 | 
						|
        (setf count len))
 | 
						|
    (dolist (item lis)
 | 
						|
      (set-rand-item-weight item (next (rand-item-weight-pattern item))))))
 | 
						|
 | 
						|
 | 
						|
(send random-class :answer :advance '()
 | 
						|
  '((let (selection (iterations 0))
 | 
						|
      ;(display "random-class :advance" mincnt repeats)
 | 
						|
      (cond ((and mincnt (< repeats mincnt))
 | 
						|
             (setf selection previous)
 | 
						|
             (incf repeats))
 | 
						|
            (t
 | 
						|
             (setf selection
 | 
						|
                   (select-random len lis previous repeats mincnt maxcnt))))
 | 
						|
      (loop ; make sure selection is ok, otherwise try again
 | 
						|
        (cond ((and (eq selection previous)
 | 
						|
                    maxcnt 
 | 
						|
                    (>= repeats maxcnt)) ; hit maximum limit, try again
 | 
						|
               (setf selection
 | 
						|
                     (select-random len lis previous repeats mincnt maxcnt))
 | 
						|
               (incf iterations)
 | 
						|
               (cond ((> iterations 10000)
 | 
						|
                      (error
 | 
						|
                        (format nil
 | 
						|
                         "~A, unable to pick next item after 10000 tries"
 | 
						|
                         name)
 | 
						|
                       lis))))
 | 
						|
              (t (return)))) ; break from loop, we found a selection
 | 
						|
 | 
						|
        ; otherwise, we are ok
 | 
						|
        (if (not (eq selection previous))
 | 
						|
            (setf repeats 1)
 | 
						|
            (incf repeats))
 | 
						|
        (setf mincnt (rand-item-min selection))
 | 
						|
        (setf maxcnt (rand-item-max selection))
 | 
						|
        (setf previous selection)
 | 
						|
        ;(display "new selection" repeats mincnt maxcnt selection)
 | 
						|
        (send self :set-current (rand-item-value selection)))))
 | 
						|
      
 | 
						|
 | 
						|
(defun make-random (lis &key for (name "random") trace)
 | 
						|
   (check-for-list-or-pattern lis "make-random")
 | 
						|
   (send random-class :new lis for name trace))
 | 
						|
 | 
						|
 | 
						|
;; ---- PALINDROME class -----
 | 
						|
 | 
						|
#| Palindrome includes elide, which is either t, nil, :first, or :last.
 | 
						|
The pattern length is the "natural" length of the pattern, which goes
 | 
						|
forward and backward through the list. Thus, if the list is of length N,
 | 
						|
the palindrome length depends on elide as follows:
 | 
						|
    elide   length
 | 
						|
     nil      2N
 | 
						|
     t        2N - 2
 | 
						|
   :first     2N - 1
 | 
						|
   :last      2N - 1
 | 
						|
If elide is a pattern, and if length is not specified, then length should
 | 
						|
be computed based on elide. 
 | 
						|
|#
 | 
						|
 | 
						|
 | 
						|
(setf palindrome-class (send class :new 
 | 
						|
                         '(lis revlis lis-pattern 
 | 
						|
                           direction elide-pattern
 | 
						|
                           elide cursor)
 | 
						|
                         '() pattern-class))
 | 
						|
 | 
						|
(send palindrome-class :answer :set-list '(l)
 | 
						|
  '((setf lis l)
 | 
						|
    (check-for-list lis "palindrome-class :start-period")
 | 
						|
    (setf is-nested (list-has-pattern lis))
 | 
						|
    (setf lis (make-homogeneous l))
 | 
						|
    (setf revlis (reverse lis)
 | 
						|
          direction t
 | 
						|
          cursor lis)))
 | 
						|
 | 
						|
 | 
						|
(send palindrome-class :answer :isnew '(l e for nm tr)
 | 
						|
  '((cond ((patternp l)
 | 
						|
           (setf lis-pattern l))
 | 
						|
          ((listp l)
 | 
						|
           (send self :set-list l))
 | 
						|
          (t
 | 
						|
           (error (format nil "~A, expected list" nm) l)))
 | 
						|
    (setf elide-pattern e length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
(send palindrome-class :answer :start-period '()
 | 
						|
  '((cond (lis-pattern
 | 
						|
           (send self :set-list (next lis-pattern t))
 | 
						|
           (setf cursor lis)))
 | 
						|
    (setf elide (next elide-pattern))
 | 
						|
    (if (and elide (null lis))
 | 
						|
        (error (format nil "~A, cannot elide if list is empty" name)))
 | 
						|
    (if (null count)
 | 
						|
        (setf count (- (* 2 (length lis))
 | 
						|
                       (if (member elide '(:first :last)) 
 | 
						|
                           1
 | 
						|
                           (if elide 2 0)))))))
 | 
						|
 | 
						|
 | 
						|
(send palindrome-class :answer :next-item '()
 | 
						|
  '((send self :set-current (car cursor))
 | 
						|
    (pop cursor)
 | 
						|
    (cond ((and cursor (not (cdr cursor))
 | 
						|
                (or (and direction (member elide '(:last t)))
 | 
						|
                    (and (not direction) (member elide '(:first t)))))
 | 
						|
           (pop cursor)))))
 | 
						|
 | 
						|
 | 
						|
(send palindrome-class :answer :advance '()
 | 
						|
  '(
 | 
						|
    (cond (cursor
 | 
						|
           (send self :next-item))
 | 
						|
          (direction ;; we're going forward
 | 
						|
           (setf direction nil) ;; now going backward
 | 
						|
           (setf cursor revlis)
 | 
						|
           (send self :next-item))
 | 
						|
          (t ;; direction is reverse
 | 
						|
           (setf direction t)
 | 
						|
           (setf cursor lis)
 | 
						|
           (send self :next-item)))))
 | 
						|
 | 
						|
 | 
						|
(defun make-palindrome (lis &key elide for (name "palindrome") trace)
 | 
						|
  (check-for-list-or-pattern lis "make-palindrome")
 | 
						|
  (send palindrome-class :new lis elide for name trace))
 | 
						|
 | 
						|
 | 
						|
;; ================= HEAP CLASS ======================
 | 
						|
 | 
						|
;; to handle the :max keyword, which tells the object to avoid
 | 
						|
;; repeating the last element of the previous period:
 | 
						|
;;
 | 
						|
;; maxcnt = 1 means "avoid the repetition"
 | 
						|
;; check-repeat signals we are at the beginning of the period and must check
 | 
						|
;; prev holds the previous value (initially nil)
 | 
						|
;; after each item is generated, check-repeat is cleared. It is
 | 
						|
;; recalculated when a new period is started.
 | 
						|
 | 
						|
(setf heap-class (send class :new '(lis used maxcnt prev check-repeat
 | 
						|
                                    lis-pattern len)
 | 
						|
                       '() pattern-class))
 | 
						|
 | 
						|
(send heap-class :answer :isnew '(l for mx nm tr)
 | 
						|
  '((cond ((patternp l)
 | 
						|
           (setf lis-pattern l))
 | 
						|
          ((listp l)
 | 
						|
           ; make a copy of l to avoid side effects
 | 
						|
           (send self :set-list (append l nil)))
 | 
						|
          (t
 | 
						|
           (error (format nil "~A, expected list" nm) l)))
 | 
						|
    (setf length-pattern for maxcnt mx name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
(send heap-class :answer :set-list '(l)
 | 
						|
  '((setf lis l)
 | 
						|
    (check-for-list lis "heap-class :set-list")
 | 
						|
    (setf is-nested (list-has-pattern lis))
 | 
						|
    (setf lis (make-homogeneous lis))
 | 
						|
    (setf len (length lis))))
 | 
						|
 | 
						|
 | 
						|
(send heap-class :answer :start-period '()
 | 
						|
  '(;(display "heap-class :start-period" lis-pattern count lis)
 | 
						|
    (cond (lis-pattern
 | 
						|
           (send self :set-list (next lis-pattern t))))
 | 
						|
    ; start of period -- may need to avoid repeating previous item
 | 
						|
    (if (= maxcnt 1) (setf check-repeat t))
 | 
						|
    (if (null count)
 | 
						|
        (setf count len))))
 | 
						|
 | 
						|
    
 | 
						|
(defun delete-first (elem lis)
 | 
						|
  (cond ((null lis) nil)
 | 
						|
        ((eq elem (car lis))
 | 
						|
         (cdr lis))
 | 
						|
        (t
 | 
						|
         (cons (car lis) (delete-first elem (cdr lis))))))
 | 
						|
 | 
						|
 | 
						|
;; NO-DISTINCT-ELEM -- check if any element of list is not val
 | 
						|
;;
 | 
						|
(defun no-distinct-elem (lis val)
 | 
						|
  (not 
 | 
						|
    (dolist (elem lis)
 | 
						|
      (if (not (equal elem val))
 | 
						|
          ;; there is a distinct element, return t from dolist
 | 
						|
          (return t)))))
 | 
						|
    ;; if no distinct element, dolist returns nil, but this is negated
 | 
						|
    ;; by the NOT so the function will return t
 | 
						|
 | 
						|
 | 
						|
(send heap-class :answer :advance '()
 | 
						|
  '((cond ((null lis)
 | 
						|
           (setf lis used)
 | 
						|
           (setf used nil)))
 | 
						|
    (let (n elem)
 | 
						|
      (cond ((and check-repeat (no-distinct-elem lis prev))
 | 
						|
             (error (format nil "~A, cannot avoid repetition, but :max is 1"
 | 
						|
                                name))))
 | 
						|
      (loop 
 | 
						|
        (setf n (random (length lis)))
 | 
						|
        (setf elem (nth n lis))
 | 
						|
        (if (or (not check-repeat) (not (equal prev elem))) 
 | 
						|
            (return))) ;; loop until suitable element is chosen
 | 
						|
      (setf lis (delete-first elem lis))
 | 
						|
      (push elem used)
 | 
						|
      (setf check-repeat nil)
 | 
						|
      (setf prev elem)
 | 
						|
      (send self :set-current elem))))
 | 
						|
 | 
						|
(defun make-heap (lis &key for (max 2) (name "heap") trace)
 | 
						|
  (send heap-class :new lis for max name trace))
 | 
						|
 | 
						|
;;================== COPIER CLASS ====================
 | 
						|
 | 
						|
(setf copier-class (send class :new '(sub-pattern repeat repeat-pattern 
 | 
						|
                                      merge merge-pattern period cursor) 
 | 
						|
                                    '() pattern-class))
 | 
						|
 | 
						|
(send copier-class :answer :isnew '(p r m for nm tr)
 | 
						|
  '((setf sub-pattern p repeat-pattern r merge-pattern m)
 | 
						|
    (setf length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
#| copier-class makes copies of periods from sub-pattern
 | 
						|
 | 
						|
If merge is true, the copies are merged into one big period.
 | 
						|
If merge is false, then repeat separate periods are returned.
 | 
						|
If repeat is negative, then -repeat periods of sub-pattern
 | 
						|
are skipped.
 | 
						|
 | 
						|
merge and repeat are computed from merge-pattern and 
 | 
						|
repeat-pattern initially and after making repeat copies
 | 
						|
 | 
						|
To repeat individual items, set the :for keyword parameter of
 | 
						|
the sub-pattern to 1.
 | 
						|
|#
 | 
						|
 | 
						|
(send copier-class :answer :start-period '()
 | 
						|
  '((cond ((null count) 
 | 
						|
           (cond ((or (null repeat) (zerop repeat))
 | 
						|
                  (send self :really-start-period))
 | 
						|
                 (t
 | 
						|
                  (setf count (length period))))))))
 | 
						|
 | 
						|
 | 
						|
(send copier-class :answer :really-start-period '()
 | 
						|
  '(;(display "copier-class :really-start-period" count)
 | 
						|
    (setf merge (next merge-pattern))
 | 
						|
    (setf repeat (next repeat-pattern))
 | 
						|
    (while (minusp repeat)
 | 
						|
      (dotimes (i (- repeat))
 | 
						|
        (setf period (next sub-pattern t)))
 | 
						|
      (setf repeat (next repeat-pattern))
 | 
						|
      (setf merge (next merge-pattern)))
 | 
						|
    (setf period (next sub-pattern t))
 | 
						|
    (setf cursor nil)
 | 
						|
    (if (null count)
 | 
						|
        (setf count (* (if merge repeat 1)
 | 
						|
                       (length period))))))
 | 
						|
 | 
						|
 | 
						|
(send copier-class :answer :advance '()
 | 
						|
  '((let ((loop-count 0))
 | 
						|
      (loop
 | 
						|
        ;(display "copier loop" repeat cursor period)
 | 
						|
        (cond (cursor
 | 
						|
               (send self :set-current (car cursor))
 | 
						|
               (pop cursor)
 | 
						|
               (return))
 | 
						|
              ((plusp repeat)
 | 
						|
               (decf repeat)
 | 
						|
               (setf cursor period))
 | 
						|
              ((> loop-count 10000)
 | 
						|
               (error (format nil
 | 
						|
                "~A, copier-class :advance encountered 10000 empty periods"
 | 
						|
                name)))
 | 
						|
              (t
 | 
						|
               (send self :really-start-period)))
 | 
						|
        (incf loop-count)))))
 | 
						|
 | 
						|
 | 
						|
(defun make-copier (sub-pattern &key for (repeat 1) merge (name "copier") trace)
 | 
						|
  (send copier-class :new sub-pattern repeat merge for name trace))
 | 
						|
   
 | 
						|
;; ================= ACCUMULATE-CLASS ===================
 | 
						|
 | 
						|
(setf accumulate-class (send class :new '(sub-pattern period cursor sum mini maxi) 
 | 
						|
                                    '() pattern-class))
 | 
						|
 | 
						|
 | 
						|
(send accumulate-class :answer :isnew '(p for nm tr mn mx)
 | 
						|
  '((setf sub-pattern p length-pattern for name nm trace tr sum 0 mini mn maxi mx)
 | 
						|
    ; (display "accumulate isnew" self nm)
 | 
						|
    ))
 | 
						|
 | 
						|
 | 
						|
#| 
 | 
						|
accumulate-class creates sums of numbers from another pattern
 | 
						|
The output periods are the same as the input periods (by default).
 | 
						|
|#
 | 
						|
 | 
						|
(send accumulate-class :answer :start-period '()
 | 
						|
  '((cond ((null count)
 | 
						|
           (send self :really-start-period)))))
 | 
						|
 | 
						|
 | 
						|
(send accumulate-class :answer :really-start-period '()
 | 
						|
  '((setf period (next sub-pattern t))
 | 
						|
    (setf cursor period)
 | 
						|
    ;(display "accumulate-class :really-start-period" period cursor count)
 | 
						|
    (if (null count)
 | 
						|
        (setf count (length period)))))
 | 
						|
 | 
						|
 | 
						|
(send accumulate-class :answer :advance '()
 | 
						|
  '((let ((loop-count 0) (minimum (next mini)) (maximum (next maxi)))
 | 
						|
      (loop
 | 
						|
        (cond (cursor
 | 
						|
               (setf sum (+ sum (car cursor)))
 | 
						|
               (cond ((and (numberp minimum) (< sum minimum))
 | 
						|
                      (setf sum minimum)))
 | 
						|
               (cond ((and (numberp maximum) (> sum maximum))
 | 
						|
                      (setf sum maximum)))
 | 
						|
               (send self :set-current sum)
 | 
						|
               (pop cursor)
 | 
						|
               (return))
 | 
						|
              ((> loop-count 10000)
 | 
						|
               (error (format nil
 | 
						|
                "~A, :advance encountered 10000 empty periods" name)))
 | 
						|
              (t
 | 
						|
               (send self :really-start-period)))
 | 
						|
        (incf loop-count)))))
 | 
						|
 | 
						|
 | 
						|
(defun make-accumulate (sub-pattern &key for min max (name "accumulate") trace)
 | 
						|
  (send accumulate-class :new sub-pattern for name trace min max))
 | 
						|
   
 | 
						|
;;================== ACCUMULATION CLASS ===================
 | 
						|
 | 
						|
;; for each item, generate all items up to and including the item, e.g.
 | 
						|
;; (a b c) -> (a a b a b c)
 | 
						|
 | 
						|
(setf accumulation-class (send class :new '(lis lis-pattern outer inner len)
 | 
						|
                               '() pattern-class))
 | 
						|
 | 
						|
(send accumulation-class :answer :isnew '(l for nm tr)
 | 
						|
  '((cond ((patternp l)
 | 
						|
           (setf lis-pattern l))
 | 
						|
          ((listp l)
 | 
						|
           (send self :set-list l))
 | 
						|
          (t
 | 
						|
           (error (format nil "~A, expected list" nm) l)))
 | 
						|
      (setf length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
(send accumulation-class :answer :set-list '(l)
 | 
						|
  '((setf lis l)
 | 
						|
    (check-for-list lis "heap-class :set-list")
 | 
						|
    (setf lis (make-homogeneous lis))
 | 
						|
    (setf inner lis)
 | 
						|
    (setf outer lis)
 | 
						|
    (setf len (length lis))))
 | 
						|
 | 
						|
(send accumulation-class :answer :start-period '()
 | 
						|
  '((cond (lis-pattern
 | 
						|
           (send self :set-list (next lis-pattern t))))
 | 
						|
    ; start of period, length = (n^2 + n) / 2
 | 
						|
    (if (null count) (setf count (/ (+ (* len len) len) 2)))))
 | 
						|
 | 
						|
(send accumulation-class :answer :advance '()
 | 
						|
  ;; inner traverses lis from first to outer
 | 
						|
  ;; outer traverses lis
 | 
						|
  '((let ((elem (car inner)))
 | 
						|
      (cond ((eq inner outer)
 | 
						|
             (setf outer (rest outer))
 | 
						|
             (setf outer (if outer outer lis))
 | 
						|
             (setf inner lis))
 | 
						|
            (t
 | 
						|
             (setf inner (rest inner))))
 | 
						|
      (send self :set-current elem))))
 | 
						|
 | 
						|
(defun make-accumulation (lis &key for (name "accumulation") trace)
 | 
						|
  (send accumulation-class :new lis for name trace))
 | 
						|
 | 
						|
 | 
						|
;;================== SUM CLASS =================
 | 
						|
 | 
						|
(setf sum-class (send class :new '(x y period cursor fn) '() pattern-class))
 | 
						|
 | 
						|
(send sum-class :answer :isnew '(xx yy for nm tr)
 | 
						|
  '((setf x xx y yy length-pattern for name nm trace tr fn #'+)))
 | 
						|
 | 
						|
#|
 | 
						|
sum-class creates pair-wise sums of numbers from 2 streams.
 | 
						|
The output periods are the same as the input periods of the first
 | 
						|
pattern argument (by default).
 | 
						|
|#
 | 
						|
 | 
						|
(send sum-class :answer :start-period '()
 | 
						|
  '((cond ((null count)
 | 
						|
           (send self :really-start-period)))))
 | 
						|
 | 
						|
(send sum-class :answer :really-start-period '()
 | 
						|
  '((setf period (next x t))
 | 
						|
    (setf cursor period)
 | 
						|
    (if (null count)
 | 
						|
        (setf count (length period)))))
 | 
						|
 | 
						|
(send sum-class :answer :advance '()
 | 
						|
  '((let ((loop-count 0) rslt)
 | 
						|
      (loop
 | 
						|
        (cond (cursor
 | 
						|
               (setf rslt (funcall fn (car cursor) (next y)))
 | 
						|
               (send self :set-current rslt)
 | 
						|
               (pop cursor)
 | 
						|
               (return))
 | 
						|
              ((> loop-count 10000)
 | 
						|
               (error (format nil
 | 
						|
                       "~A, :advance encountered 10000 empty periods" name)))
 | 
						|
              (t
 | 
						|
               (send self :really-start-period)))
 | 
						|
        (incf loop-count)))))
 | 
						|
 | 
						|
 | 
						|
(defun make-sum (x y &key for (name "sum") trace)
 | 
						|
  (send sum-class :new x y for name trace))               
 | 
						|
 | 
						|
 | 
						|
;;================== PRODUCT CLASS =================
 | 
						|
 | 
						|
(setf product-class (send class :new '() '() sum-class))
 | 
						|
 | 
						|
(send product-class :answer :isnew '(xx yy for nm tr)
 | 
						|
  '((setf x xx y yy length-pattern for name nm trace tr fn #'*)))
 | 
						|
 | 
						|
(defun make-product (x y &key for (name "product") trace)
 | 
						|
  (send product-class :new x y for name trace))               
 | 
						|
 | 
						|
 | 
						|
;;================== EVAL CLASS =================
 | 
						|
 | 
						|
(setf eval-class (send class :new '(expr expr-pattern) 
 | 
						|
                       '() pattern-class))
 | 
						|
 | 
						|
(send eval-class :answer :isnew '(e for nm tr)
 | 
						|
  '((cond ((patternp e)
 | 
						|
           (setf expr-pattern e))
 | 
						|
          (t
 | 
						|
           (setf expr e)))
 | 
						|
    (setf length-pattern for name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
(send eval-class :answer :start-period '()
 | 
						|
  '(;(display "cycle-class :start-period" lis-pattern lis count length-pattern)
 | 
						|
    (cond (expr-pattern
 | 
						|
           (setf expr (next expr-pattern))))))
 | 
						|
  
 | 
						|
 | 
						|
(send eval-class :answer :advance '()
 | 
						|
  '((send self :set-current (eval expr))))
 | 
						|
 | 
						|
 | 
						|
(defun make-eval (expr &key (for 1) (name "eval") trace)
 | 
						|
   (send eval-class :new expr for name trace))
 | 
						|
 | 
						|
;;================== MARKOV CLASS ====================
 | 
						|
 | 
						|
(setf markov-class (send class :new 
 | 
						|
      '(rules order state produces pattern len) 
 | 
						|
      '() pattern-class))
 | 
						|
 | 
						|
 | 
						|
(defun is-produces-homogeneous (produces)
 | 
						|
  (let (type elem)
 | 
						|
    (setf *rslt* nil)
 | 
						|
    (loop
 | 
						|
      (cond ((or (null produces) (eq produces :eval) (null (cadr produces)))
 | 
						|
             (return t)))
 | 
						|
      (setf elem (cadr produces))
 | 
						|
      (cond ((null type)
 | 
						|
             (setf type (if (patternp elem) 'pattern 'atom))
 | 
						|
           ;(display "is-produces-homogeneous" type)
 | 
						|
             (setf *rslt* (eq type 'pattern))
 | 
						|
             ;(display "is-produces-homogeneous" *rslt*)
 | 
						|
             )
 | 
						|
            ((and (eq type 'pattern) (not (patternp elem)))
 | 
						|
             (return nil))
 | 
						|
            ((and (eq type 'atom)
 | 
						|
                  (patternp elem))
 | 
						|
             (return nil)))
 | 
						|
      (setf produces (cddr produces)))))
 | 
						|
 | 
						|
 | 
						|
(defun make-produces-homogeneous (produces)
 | 
						|
  (let (result item)
 | 
						|
    (loop
 | 
						|
      (if (null produces) (return nil))
 | 
						|
      (push (car produces) result)
 | 
						|
      (setf produces (cdr produces))
 | 
						|
      (setf item (car produces))
 | 
						|
      (setf produces (cdr produces))
 | 
						|
      (if (not (patternp item)) 
 | 
						|
        (setf item (make-cycle (list item))))
 | 
						|
      (push item result))
 | 
						|
    (reverse result)))
 | 
						|
 | 
						|
 | 
						|
(send markov-class :answer :isnew '(r o s p for nm tr)
 | 
						|
  ;; input parameters are rules, order, state, produces, for, name, trace
 | 
						|
  '((setf order o state s produces p length-pattern for name nm trace tr)
 | 
						|
    (setf len (length r))
 | 
						|
    ;; input r looks like this:
 | 
						|
    ;; ((prev1 prev2 -> next1 next2 (next3 weight) ... ) ...)
 | 
						|
    ;; transition table will look like a list of these:
 | 
						|
    ;; ((prev1 prev2 ... prevn) (next1 weight weight-pattern) ...)
 | 
						|
    (dolist (rule r)
 | 
						|
      (let ((targets (cdr (nthcdr order rule)))
 | 
						|
            entry pattern)
 | 
						|
        ;; build entry in reverse order
 | 
						|
        (dolist (target targets)
 | 
						|
          (push (if (atom target)
 | 
						|
                    (list target 1 1) 
 | 
						|
                    (list (first target) 
 | 
						|
                          (next (second target)) 
 | 
						|
                          (second target))) 
 | 
						|
                entry))
 | 
						|
        ; (display "isnew" entry rule targets order (nthcdr order rule))
 | 
						|
        (dotimes (i order)
 | 
						|
          (push (nth i rule) pattern))
 | 
						|
        (push (cons (reverse pattern) entry) rules)))
 | 
						|
    (setf rules (reverse rules)) ;; keep rules in original order
 | 
						|
    (setf *rslt* nil) ;; in case produces is nil
 | 
						|
    (cond ((and produces (not (is-produces-homogeneous produces)))
 | 
						|
           (setf produces (make-produces-homogeneous produces))))
 | 
						|
    ;(display "markov-class :isnew" *rslt*)
 | 
						|
    (setf is-nested *rslt*) ;; returned by is-produces-homogeneous
 | 
						|
    ;(display "markov-class :isnew" is-nested)
 | 
						|
    ))
 | 
						|
 | 
						|
 | 
						|
(defun markov-match (state pattern)
 | 
						|
  (dolist (p pattern t) ;; return true if no mismatch
 | 
						|
    ;; compare element-by-element
 | 
						|
    (cond ((eq p '*)) ; anything matches '*
 | 
						|
          ((eql p (car state)))
 | 
						|
          (t (return nil))) ; a mismatch: return false
 | 
						|
    (setf state (cdr state))))
 | 
						|
 | 
						|
(defun markov-sum-of-weights (rule)
 | 
						|
  ;(display "sum-of-weights" rule)
 | 
						|
  (let ((sum 0.0))
 | 
						|
    (dolist (target (cdr rule))
 | 
						|
      ;(display "markov-sum-of-weights" target)
 | 
						|
      (setf sum (+ sum (second target))))
 | 
						|
    sum))
 | 
						|
 | 
						|
 | 
						|
(defun markov-pick-target (sum rule)
 | 
						|
  (let ((total 0.0)
 | 
						|
        ;; want to choose a value in the interval [0, sum)
 | 
						|
        ;; but real-random is not open on the right, so fudge
 | 
						|
        ;; the range by a small amount:
 | 
						|
        (r (real-random 0.0 (- sum SCORE-EPSILON))))
 | 
						|
    (dolist (target (cdr rule))
 | 
						|
      (setf total (+ total (second target)))
 | 
						|
      (cond ((> total r) (return (car target)))))))
 | 
						|
 | 
						|
 | 
						|
(defun markov-update-weights (rule)
 | 
						|
  (dolist (target (cdr rule))
 | 
						|
    (setf (car (cdr target)) (next (caddr target)))))
 | 
						|
 | 
						|
 | 
						|
(defun markov-map-target (target produces)
 | 
						|
  (while (and produces (not (eq target (car produces))))
 | 
						|
    (setf produces (cddr produces)))
 | 
						|
  (cadr produces))
 | 
						|
 | 
						|
 | 
						|
(send markov-class :answer :find-rule '()
 | 
						|
  '((let (rslt)
 | 
						|
      ;(display "find-rule" rules)
 | 
						|
      (dolist (rule rules)
 | 
						|
        ;(display "find-rule" state rule)
 | 
						|
        (cond ((markov-match state (car rule))
 | 
						|
               (setf rslt rule)
 | 
						|
               (return rslt))))
 | 
						|
      (cond ((null rslt)
 | 
						|
             (display "Error, no matching rule found" state rules)
 | 
						|
             (error (format nil "~A, (markov-class)" name))))
 | 
						|
      rslt)))
 | 
						|
 | 
						|
 | 
						|
(send markov-class :answer :start-period '()
 | 
						|
  '((if (null count)
 | 
						|
        (setf count len))))
 | 
						|
 | 
						|
(defun markov-general-rule-p (rule)
 | 
						|
  (let ((pre (car rule)))
 | 
						|
    (cond ((< (length pre) 2) nil) ;; 1st-order mm
 | 
						|
          (t
 | 
						|
           ;; return false if any member not *
 | 
						|
           ;; return t if all members are *
 | 
						|
           (dolist (s pre t)
 | 
						|
             (if (eq s '*) t (return nil)))))))
 | 
						|
 | 
						|
(defun markov-find-state-leading-to (target rules)
 | 
						|
  (let (candidates)
 | 
						|
    (dolist (rule rules)
 | 
						|
      (let ((targets (cdr rule)))
 | 
						|
        (dolist (targ targets)
 | 
						|
          (cond ((eql (car targ) target)
 | 
						|
                 (push (car rule) candidates))))))
 | 
						|
    (cond (candidates ;; found at least one
 | 
						|
           (nth (random (length candidates)) candidates))
 | 
						|
          (t
 | 
						|
           nil))))
 | 
						|
 | 
						|
(send markov-class :answer :advance '()
 | 
						|
  '((let (rule sum target rslt new-state)
 | 
						|
      ;(display "markov" pattern rules)
 | 
						|
      (setf rule (send self :find-rule))
 | 
						|
      ;(display "advance 1" rule)
 | 
						|
      (markov-update-weights rule)
 | 
						|
      ;(display "advance 2" rule)
 | 
						|
      (setf sum (markov-sum-of-weights rule))
 | 
						|
      ;; the target can be a pattern, so apply NEXT to it
 | 
						|
      (setf target (next (markov-pick-target sum rule)))
 | 
						|
      ;; if the matching rule is multiple *'s, then this
 | 
						|
      ;; is a higher-order Markov model, and we may now
 | 
						|
      ;; wander around in parts of the state space that
 | 
						|
      ;; never appeared in the training data. To avoid this
 | 
						|
      ;; we violate the strict interpretation of the rules
 | 
						|
      ;; and pick a random state sequence from the rule set
 | 
						|
      ;; that might have let to the current state. We jam
 | 
						|
      ;; this state sequence into state so that when we
 | 
						|
      ;; append target, we'll have a history that might
 | 
						|
      ;; have a corresponding rule next time.
 | 
						|
      (cond ((markov-general-rule-p rule)
 | 
						|
             (setf new-state (markov-find-state-leading-to target rules))
 | 
						|
             (cond (new-state
 | 
						|
                    ;(display "state replacement" new-state target)
 | 
						|
                    (setf state new-state)))))
 | 
						|
      (setf state (append (cdr state) (list target)))
 | 
						|
      ;(display "markov next" rule sum target state)
 | 
						|
      ;; target is the symbol for the current state. We can
 | 
						|
      ;; return target (default), the value of target, or a
 | 
						|
      ;; mapped value:
 | 
						|
      (cond ((eq produces :eval)
 | 
						|
             (setf target (eval target)))
 | 
						|
            ((and produces (listp produces))
 | 
						|
             ;(display "markov-produce" target produces)
 | 
						|
             (setf target (markov-map-target target produces))))
 | 
						|
      (if (not (eq is-nested (patternp target)))
 | 
						|
          (error (format nil 
 | 
						|
         "~A :is-nested keyword (~A) not consistent with result (~A)"
 | 
						|
                  name is-nested target)))
 | 
						|
      (send self :set-current target))))
 | 
						|
 | 
						|
 | 
						|
(defun make-markov (rules &key produces past for (name "markov") trace)
 | 
						|
  ;; check to make sure past and rules are consistent
 | 
						|
  (let ((order (length past)))
 | 
						|
    (dolist (rule rules)
 | 
						|
      (dotimes (i order)
 | 
						|
        (if (eq (car rule) '->)
 | 
						|
            (error (format nil "~A, a rule does not match the length of :past"
 | 
						|
                               name)))
 | 
						|
        (pop rule))
 | 
						|
      (if (eq (car rule) '->) nil
 | 
						|
          (error (format nil "~A, a rule does not match the length of :past"
 | 
						|
                             name)))))
 | 
						|
  (cond ((null for)
 | 
						|
         (setf for (length rules))))
 | 
						|
  (send markov-class :new rules (length past) past produces for name trace))
 | 
						|
 | 
						|
 | 
						|
(defun markov-rule-match (rule state)
 | 
						|
  (cond ((null state) t)
 | 
						|
        ((eql (car rule) (car state))
 | 
						|
         (markov-rule-match (cdr rule) (cdr state)))
 | 
						|
        (t nil)))
 | 
						|
 | 
						|
 | 
						|
(defun markov-find-rule (rules state)
 | 
						|
  (dolist (rule rules)
 | 
						|
    ;(display "find-rule" rule)
 | 
						|
    (cond ((markov-rule-match rule state)
 | 
						|
           (return rule)))))
 | 
						|
 | 
						|
;; ------- functions below are for MARKOV-CREATE-RULES --------
 | 
						|
 | 
						|
;; MARKOV-FIND-CHOICE -- given a next state, find it in rule
 | 
						|
;;
 | 
						|
;; use state to get the order of the Markov model, e.g. how
 | 
						|
;; many previous states to skip in the rule, (add 1 for '->).
 | 
						|
;; then use assoc to do a quick search
 | 
						|
;;
 | 
						|
;; example:
 | 
						|
;;  (markov-find-choice '(a b -> (c 1) (d 2)) '(a b) 'd)
 | 
						|
;; returns (d 2) from the rule
 | 
						|
;;
 | 
						|
(defun markov-find-choice (rule state next)
 | 
						|
  (assoc next (nthcdr (1+ (length state)) rule)))
 | 
						|
 | 
						|
(defun markov-update-rule (rule state next)
 | 
						|
  (let ((choice (markov-find-choice rule state next)))
 | 
						|
    (cond (choice
 | 
						|
           (setf (car (cdr choice)) (1+ (cadr choice))))
 | 
						|
          (t
 | 
						|
           (nconc rule (list (list next 1)))))
 | 
						|
    rule))
 | 
						|
 | 
						|
 | 
						|
(defun markov-update-rules (rules state next)
 | 
						|
  (let ((rule (markov-find-rule rules state)))
 | 
						|
    (cond (rule
 | 
						|
           (markov-update-rule rule state next))
 | 
						|
          (t
 | 
						|
           (setf rules
 | 
						|
                 (nconc rules 
 | 
						|
                        (list (append state
 | 
						|
                                      (cons '-> (list 
 | 
						|
                                                 (list next 1)))))))))
 | 
						|
    rules))
 | 
						|
 | 
						|
 | 
						|
;; MARKOV-UPDATE-HISTOGRAM -- keep a list of symbols and counts
 | 
						|
;; 
 | 
						|
;; This histogram will become the right-hand part of a rule, so
 | 
						|
;; the format is ((symbol count) (symbol count) ...)
 | 
						|
;;
 | 
						|
(defun markov-update-histogram (histogram next)
 | 
						|
  (let ((pair (assoc next histogram)))
 | 
						|
    (cond (pair
 | 
						|
           (setf (car (cdr pair)) (1+ (cadr pair))))
 | 
						|
          (t
 | 
						|
           (setf histogram (cons (list next 1) histogram))))
 | 
						|
    histogram))
 | 
						|
 | 
						|
 | 
						|
(defun markov-create-rules (sequence order &optional generalize)
 | 
						|
  (let ((seqlen (length sequence)) state rules next histogram rule)
 | 
						|
    (cond ((<= seqlen order)
 | 
						|
           (error "markov-create-rules: sequence must be longer than order"))
 | 
						|
          ((< order 1)
 | 
						|
           (error "markov-create-rules: order must be 1 or greater")))
 | 
						|
    ; build initial state sequence
 | 
						|
    (dotimes (i order)
 | 
						|
      (setf state (nconc state (list (car sequence))))
 | 
						|
      (setf sequence (cdr sequence)))
 | 
						|
    ; for each symbol, either update a rule or add a rule
 | 
						|
    (while sequence
 | 
						|
      (setf next (car sequence))
 | 
						|
      (setf sequence (cdr sequence))
 | 
						|
      (setf rules (markov-update-rules rules state next))
 | 
						|
      (setf histogram (markov-update-histogram histogram next))
 | 
						|
      ; shift next state onto current state list
 | 
						|
      (setf state (nconc (cdr state) (list next))))
 | 
						|
    ; generalize?
 | 
						|
    (cond (generalize
 | 
						|
           (setf rule (cons '-> histogram))
 | 
						|
           (dotimes (i order)
 | 
						|
             (setf rule (cons '* rule)))
 | 
						|
           (setf rules (nconc rules (list rule)))))
 | 
						|
    rules))
 | 
						|
 | 
						|
 | 
						|
;; ----- WINDOW Class ---------
 | 
						|
 | 
						|
(setf window-class (send class :new 
 | 
						|
                         '(pattern skip-pattern lis cursor)
 | 
						|
                         '() pattern-class))
 | 
						|
 | 
						|
(send window-class :answer :isnew '(p for sk nm tr)
 | 
						|
  '((setf pattern p length-pattern for skip-pattern sk name nm trace tr)))
 | 
						|
 | 
						|
 | 
						|
(send window-class :answer :start-period '()
 | 
						|
  '((if (null count) (error (format nil "~A, :start-period -- count is null"
 | 
						|
                                        name)))
 | 
						|
    (cond ((null lis) ;; first time
 | 
						|
           (dotimes (i count)
 | 
						|
             (push (next pattern) lis))
 | 
						|
           (setf lis (reverse lis)))
 | 
						|
          (t
 | 
						|
           (let ((skip (next skip-pattern)))
 | 
						|
             (dotimes (i skip)
 | 
						|
               (if lis (pop lis) (next pattern))))
 | 
						|
           (setf lis (reverse lis))
 | 
						|
           (let ((len (length lis)))
 | 
						|
             (while (< len count)
 | 
						|
               (incf len)
 | 
						|
               (push (next pattern) lis))
 | 
						|
             (while (> len count)
 | 
						|
               (decf len)
 | 
						|
               (pop lis))
 | 
						|
             (setf lis (reverse lis)))))
 | 
						|
    (setf cursor lis)))
 | 
						|
 | 
						|
 | 
						|
(send window-class :answer :advance '()
 | 
						|
  '((send self :set-current (car cursor))
 | 
						|
    (pop cursor)))
 | 
						|
 | 
						|
(defun make-window (pattern length-pattern skip-pattern
 | 
						|
                    &key (name "window") trace)
 | 
						|
  (send window-class :new pattern length-pattern skip-pattern name trace))
 | 
						|
 | 
						|
;; SCORE-SORTED -- test if score is sorted
 | 
						|
;;
 | 
						|
(defun score-sorted (score)
 | 
						|
  (let ((result t))
 | 
						|
    (while (cdr score)
 | 
						|
      (cond ((event-before (cadr score) (car score))
 | 
						|
             (setf result nil)
 | 
						|
             (return nil)))
 | 
						|
      (setf score (cdr score)))
 | 
						|
    result))
 | 
						|
    
 | 
						|
 | 
						|
(defmacro score-gen (&rest args)
 | 
						|
  (let (key val tim dur (name ''note) ioi trace save 
 | 
						|
        score-len score-dur others pre post
 | 
						|
        next-expr (score-begin 0) score-end)
 | 
						|
    (while (and args (cdr args))
 | 
						|
      (setf key (car args))
 | 
						|
      (setf val (cadr args))
 | 
						|
      (setf args (cddr args))       
 | 
						|
      (case key
 | 
						|
        (:time (setf tim val))
 | 
						|
        (:dur (setf dur val))
 | 
						|
        (:name (setf name val))
 | 
						|
        (:ioi (setf ioi val))
 | 
						|
        (:trace (setf trace val))
 | 
						|
        (:save (setf save val))
 | 
						|
        (:pre (setf pre val))
 | 
						|
        (:post (setf post val))
 | 
						|
        (:score-len (setf score-len val))
 | 
						|
        (:score-dur (setf score-dur val))
 | 
						|
        (:begin (setf score-begin val))
 | 
						|
        (:end (setf score-end val))
 | 
						|
        (t (setf others (cons key (cons val others))))))
 | 
						|
    ;; make sure at least one of score-len, score-dur is present
 | 
						|
    (cond ((and (null score-len) (null score-dur))
 | 
						|
           (error
 | 
						|
           "score-gen needs either :score-len or :score-dur to limit length")))
 | 
						|
    ;; compute expression for dur
 | 
						|
    (cond ((null dur)
 | 
						|
           (setf dur 'sg:ioi)))
 | 
						|
    ;; compute expression for ioi
 | 
						|
    (cond ((null ioi)
 | 
						|
           (setf ioi 1)))
 | 
						|
    ;; compute expression for next start time
 | 
						|
    (setf next-expr '(+ sg:start sg:ioi))
 | 
						|
    ; (display "score-gen" others)
 | 
						|
    `(let (sg:seq (sg:start ,score-begin) sg:ioi 
 | 
						|
           (sg:score-len ,score-len) (sg:score-dur ,score-dur)
 | 
						|
           (sg:count 0) (sg:save ,save) 
 | 
						|
           (sg:begin ,score-begin) (sg:end ,score-end))
 | 
						|
       ;; make sure at least one of score-len, score-dur is present
 | 
						|
       (loop
 | 
						|
         (cond ((or (and sg:score-len (<= sg:score-len sg:count))
 | 
						|
                    (and sg:score-dur (<= (+ sg:begin sg:score-dur) sg:start)))
 | 
						|
                (return)))
 | 
						|
         ,pre
 | 
						|
         ,(cond (tim (list 'setf 'sg:start tim)))
 | 
						|
         (setf sg:ioi ,ioi)
 | 
						|
         (setf sg:dur ,dur)
 | 
						|
         (push (list sg:start sg:dur (list ,name ,@others))
 | 
						|
               sg:seq)
 | 
						|
         ,post
 | 
						|
         (cond (,trace
 | 
						|
                (format t "get-seq trace at ~A stretch ~A: ~A~%" 
 | 
						|
                          sg:start sg:dur (car sg:seq))))
 | 
						|
         (incf sg:count)
 | 
						|
         (setf sg:start ,next-expr))
 | 
						|
       (setf sg:seq (reverse sg:seq))
 | 
						|
       ;; avoid sorting a sorted list -- XLisp's quicksort can overflow the
 | 
						|
       ;; stack if the list is sorted because (apparently) the pivot points
 | 
						|
       ;; are not random.
 | 
						|
       (cond ((not (score-sorted sg:seq))
 | 
						|
              (setf sg:seq (bigsort sg:seq #'event-before))))
 | 
						|
       (cond ((and sg:seq (null sg:end))
 | 
						|
              (setf sg:end (event-end (car (last sg:seq)))))
 | 
						|
             ((null sg:end)
 | 
						|
              (setf sg:end 0)))
 | 
						|
       (push (list 0 0 (list 'SCORE-BEGIN-END ,score-begin sg:end)) sg:seq)
 | 
						|
       (cond (sg:save (set sg:save sg:seq)))
 | 
						|
       sg:seq)))
 | 
						|
 | 
						|
;; ============== score manipulation ===========
 | 
						|
 | 
						|
(defun event-before (a b)
 | 
						|
  (< (car a) (car b)))
 | 
						|
 | 
						|
;; EVENT-END -- get the ending time of a score event
 | 
						|
;;
 | 
						|
(defun event-end (e) (+ (car e) (cadr e)))
 | 
						|
 | 
						|
;; EVENT-TIME -- time of an event
 | 
						|
;;
 | 
						|
(setfn event-time car)
 | 
						|
 | 
						|
;; EVENT-DUR -- duration of an event
 | 
						|
;;
 | 
						|
(setfn event-dur cadr)
 | 
						|
 | 
						|
;; EVENT-SET-TIME -- new event with new time
 | 
						|
;;
 | 
						|
(defun event-set-time (event time)
 | 
						|
  (cons time (cdr event)))
 | 
						|
 | 
						|
 | 
						|
;; EVENT-SET-DUR -- new event with new dur
 | 
						|
;;
 | 
						|
(defun event-set-dur (event dur)
 | 
						|
  (list (event-time event) 
 | 
						|
        dur 
 | 
						|
        (event-expression event)))
 | 
						|
  
 | 
						|
  
 | 
						|
;; EVENT-SET-EXPRESSION -- new event with new expression
 | 
						|
;;
 | 
						|
(defun event-set-expression (event expression)
 | 
						|
  (list (event-time event) 
 | 
						|
        (event-dur event)
 | 
						|
        expression))
 | 
						|
  
 | 
						|
;; EXPR-HAS-ATTR -- test if expression has attribute
 | 
						|
;;
 | 
						|
(defun expr-has-attr (expression attr)
 | 
						|
  (member attr expression))
 | 
						|
 | 
						|
 | 
						|
;; EXPR-GET-ATTR -- get value of attribute from expression
 | 
						|
;;
 | 
						|
(defun expr-get-attr (expression attr &optional default)
 | 
						|
  (let ((m (member attr expression)))
 | 
						|
    (if m (cadr m) default)))
 | 
						|
 | 
						|
 | 
						|
;; EXPR-SET-ATTR -- set value of an attribute in expression
 | 
						|
;; (returns new expression)
 | 
						|
(defun expr-set-attr (expr attr value)
 | 
						|
  (cons (car expr) (expr-parameters-set-attr (cdr expr) attr value)))
 | 
						|
 | 
						|
(defun expr-parameters-set-attr (lis attr value)
 | 
						|
  (cond ((null lis) (list attr value))
 | 
						|
        ((eq (car lis) attr) (cons attr (cons value (cddr lis))))
 | 
						|
        (t (cons (car lis) 
 | 
						|
                 (cons (cadr lis) 
 | 
						|
                       (expr-parameters-set-attr (cddr lis) attr value))))))
 | 
						|
 | 
						|
 | 
						|
;; EXPR-REMOVE-ATTR -- expression without attribute value pair
 | 
						|
(defun expr-remove-attr (event attr)
 | 
						|
  (cons (car expr) (expr-parameters-remove-attr (cdr expr) attr)))
 | 
						|
 | 
						|
(defun expr-parameters-remove-attr (lis attr)
 | 
						|
   (cond ((null lis) nil)
 | 
						|
         ((eq (car lis) attr) (cddr lis))
 | 
						|
         (t (cons (car lis)
 | 
						|
                  (cons (cadr lis)
 | 
						|
                        (expr-parameters-remove-attr (cddr lis) attr))))))
 | 
						|
 | 
						|
 | 
						|
;; EVENT-GET-ATTR -- get value of attribute from event
 | 
						|
;;
 | 
						|
(defun event-get-attr (note attr &optional default)
 | 
						|
  (expr-get-attr (event-expression note) attr default))
 | 
						|
 | 
						|
 | 
						|
;; EVENT-SET-ATTR -- new event with attribute = value
 | 
						|
(defun event-set-attr (event attr value)
 | 
						|
  (event-set-expression 
 | 
						|
    event
 | 
						|
    (expr-set-attr (event-expression event) attr value)))
 | 
						|
 | 
						|
 | 
						|
;; EVENT-REMOVE-ATTR -- new event without atttribute value pair
 | 
						|
(defun event-remove-attr (event attr)
 | 
						|
  (event-set-expression
 | 
						|
     event
 | 
						|
     (event-remove-attr (event-expression event) attr)))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-GET-BEGIN -- get the begin time of a score
 | 
						|
;;
 | 
						|
(defun score-get-begin (score)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (cadr (event-expression (car score))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-SET-BEGIN -- set the begin time of a score
 | 
						|
;;
 | 
						|
(defun score-set-begin (score time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (cons (list 0 0 (list 'score-begin-end time 
 | 
						|
                        (caddr (event-expression (car score)))))
 | 
						|
        (cdr score)))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-GET-END -- get the end time of a score
 | 
						|
;;
 | 
						|
(defun score-get-end (score)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (caddr (event-expression (car score))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-SET-END -- set the end time of a score
 | 
						|
;;
 | 
						|
(defun score-set-end (score time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (cons (list 0 0 (list 'score-begin-end 
 | 
						|
                        (cadr (event-expression (car score))) time))
 | 
						|
        (cdr score)))
 | 
						|
 | 
						|
 | 
						|
;; FIND-FIRST-NOTE -- use keywords to find index of first selected note
 | 
						|
;;
 | 
						|
(defun find-first-note (score from-index from-time)
 | 
						|
  (let ((s (cdr score)))
 | 
						|
    ;; offset by one because we removed element 0
 | 
						|
    (setf from-index (if from-index (max 0 (- from-index 1)) 0))
 | 
						|
    (setf from-time (if from-time 
 | 
						|
                        (- from-time SCORE-EPSILON)
 | 
						|
                        (- SCORE-EPSILON)))
 | 
						|
    (if s (setf s (nthcdr from-index s)))
 | 
						|
    
 | 
						|
    (while (and s (>= from-time (event-time (car s))))
 | 
						|
      (setf s (cdr s))
 | 
						|
      (incf from-index))
 | 
						|
    (1+ from-index)))
 | 
						|
 | 
						|
 | 
						|
;; EVENT-BEFORE -- useful function for sorting scores
 | 
						|
;;
 | 
						|
(defun event-before (a b)
 | 
						|
  (< (car a) (car b)))
 | 
						|
  
 | 
						|
;; bigsort -- a sort routine that avoids recursion in order
 | 
						|
;; to sort large lists without overflowing the evaluation stack
 | 
						|
;;
 | 
						|
;; Does not modify input list. Does not minimize cons-ing.
 | 
						|
;;
 | 
						|
;; Algorithm: first accumulate sorted sub-sequences into lists
 | 
						|
;; Then merge pairs iteratively until only one big list remains
 | 
						|
;; 
 | 
						|
(defun bigsort (lis cmp) ; sort lis using cmp function
 | 
						|
  ;; if (funcall cmp a b) then a and b are in order
 | 
						|
  (prog (rslt sub pairs)
 | 
						|
    ;; first, convert to sorted sublists stored on rslt
 | 
						|
    ;; accumulate sublists in sub
 | 
						|
   get-next-sub
 | 
						|
    (if (null lis) (go done-1))
 | 
						|
    (setf sub (list (car lis)))
 | 
						|
    (setf lis (cdr lis))
 | 
						|
   fill-sub
 | 
						|
    ;; invariant: sub is non-empty, in reverse order
 | 
						|
    (cond ((and lis (funcall cmp (car sub) (car lis)))
 | 
						|
           (setf sub (cons (car lis) sub))
 | 
						|
           (setf lis (cdr lis))
 | 
						|
           (go fill-sub)))
 | 
						|
    (setf sub (reverse sub)) ;; put sub in correct order
 | 
						|
    (setf rslt (cons sub rslt)) ;; build rslt in reverse order
 | 
						|
    (go get-next-sub)
 | 
						|
   done-1
 | 
						|
    ;; invariant: rslt is list of sorted sublists
 | 
						|
    (if (cdr rslt) nil (go done-2))
 | 
						|
    ;; invariant: rslt has at least one list
 | 
						|
    (setf pairs rslt)
 | 
						|
    (setf rslt nil)
 | 
						|
   merge-pairs    ;; merge a pair and save on rslt
 | 
						|
    (if (car pairs) nil (go end-of-pass)) ;; loop until all pairs merged
 | 
						|
    ;; invariant: pairs has at least one list
 | 
						|
    (setf list1 (car pairs)) ;; list1 is non-empty
 | 
						|
    (setf list2 (cadr pairs)) ;; list2 could be empty
 | 
						|
    (setf pairs (cddr pairs))
 | 
						|
    (cond (list2
 | 
						|
           (setf rslt (cons (list-merge list1 list2 cmp) rslt)))
 | 
						|
          (t
 | 
						|
           (setf rslt (cons list1 rslt))))
 | 
						|
    (go merge-pairs)
 | 
						|
   end-of-pass
 | 
						|
    (go done-1)
 | 
						|
   done-2
 | 
						|
    ;; invariant: rslt has one sorted list!
 | 
						|
    (return (car rslt))))
 | 
						|
 | 
						|
(defun list-merge (list1 list2 cmp)
 | 
						|
  (prog (rslt)
 | 
						|
   merge-loop
 | 
						|
    (cond ((and list1 list2)
 | 
						|
           (cond ((funcall cmp (car list1) (car list2))
 | 
						|
                  (setf rslt (cons (car list1) rslt))
 | 
						|
                  (setf list1 (cdr list1)))
 | 
						|
                 (t
 | 
						|
                  (setf rslt (cons (car list2) rslt))
 | 
						|
                  (setf list2 (cdr list2)))))
 | 
						|
          (list1
 | 
						|
           (return (nconc (reverse rslt) list1)))
 | 
						|
          (t
 | 
						|
           (return (nconc (reverse rslt) list2))))
 | 
						|
    (go merge-loop)))  
 | 
						|
 | 
						|
 | 
						|
;; SCORE-SORT -- sort a score into time order
 | 
						|
;;
 | 
						|
(defun score-sort (score &optional (copy-flag t)) 
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((begin-end (car score)))
 | 
						|
    (setf score (cdr score))
 | 
						|
    (if copy-flag (setf score (append score nil)))
 | 
						|
    (cons begin-end (bigsort score #'event-before))))
 | 
						|
  
 | 
						|
 | 
						|
;; PUSH-SORT -- insert an event in (reverse) sorted order
 | 
						|
;;
 | 
						|
;; Note: Score should NOT have a score-begin-end expression
 | 
						|
;;
 | 
						|
(defun push-sort (event score)
 | 
						|
  (let (insert-after)
 | 
						|
    (cond ((null score) (list event))
 | 
						|
          ((event-before (car score) event)
 | 
						|
           (cons event score))
 | 
						|
          (t
 | 
						|
           (setf insert-after score)
 | 
						|
           (while (and (cdr insert-after) 
 | 
						|
                       (event-before event (cadr insert-after)))
 | 
						|
             (setf insert-after (cdr insert-after)))
 | 
						|
           (setf (cdr insert-after) (cons event (cdr insert-after)))
 | 
						|
           score))))
 | 
						|
 | 
						|
 | 
						|
(setf FOREVER 3600000000.0) ; 1 million hours
 | 
						|
 | 
						|
;; FIND-LAST-NOTE -- use keywords to find index beyond last selected note
 | 
						|
;;
 | 
						|
;; note that the :to-index keyword is the index of the last note (numbered
 | 
						|
;; from zero), whereas this function returns the index of the last note
 | 
						|
;; plus one, i.e. selected notes have an index *less than* this one
 | 
						|
;;
 | 
						|
(defun find-last-note (score to-index to-time)
 | 
						|
  ;; skip past score-begin-end event
 | 
						|
  (let ((s (cdr score))
 | 
						|
        (n 1))
 | 
						|
    (setf to-index (if to-index (1+ to-index) (length score)))
 | 
						|
    (setf to-time (if to-time (- to-time SCORE-EPSILON)  FOREVER))
 | 
						|
    (while (and s (< n to-index) (< (event-time (car s)) to-time))
 | 
						|
      (setf s (cdr s))
 | 
						|
      (incf n))
 | 
						|
    n))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-MUST-HAVE-BEGIN-END -- add score-begin-end event if necessary
 | 
						|
;;
 | 
						|
(defun score-must-have-begin-end (score)
 | 
						|
  (cond ((null score) 
 | 
						|
         (list (list 0 0 (list 'SCORE-BEGIN-END 0 0))))
 | 
						|
        ((eq (car (event-expression (car score))) 'SCORE-BEGIN-END)
 | 
						|
         score)
 | 
						|
        (t (cons (list 0 0 (list 'SCORE-BEGIN-END (event-time (car score))
 | 
						|
                                 (event-end (car (last score)))))
 | 
						|
                 score))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-SHIFT -- add offset to times of score events
 | 
						|
;;
 | 
						|
(defun score-shift (score offset &key from-index to-index from-time to-time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((i 1) 
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        (end (caddr (event-expression (car score))))
 | 
						|
        result)
 | 
						|
    (dolist (event (cdr score))
 | 
						|
      (cond ((and (<= start i) (< i stop))
 | 
						|
             (setf event (event-set-time 
 | 
						|
                          event (+ (event-time event) offset)))
 | 
						|
             (setf end (max end (event-end event)))))
 | 
						|
      (setf result (push-sort event result))
 | 
						|
      (incf i))
 | 
						|
    (cons (list 0 0 (list 'SCORE-BEGIN-END
 | 
						|
                          (cadr (event-expression (car score)))
 | 
						|
                          end))
 | 
						|
          (reverse result))))
 | 
						|
 | 
						|
 | 
						|
;; TIME-STRETCH -- map a timestamp according to stretch factor
 | 
						|
;;
 | 
						|
(defun time-stretch (time stretch start-time stop-time)
 | 
						|
  (cond ((< time start-time) time)
 | 
						|
        ((< time stop-time) 
 | 
						|
         (+ start-time (* stretch (- time start-time))))
 | 
						|
        (t ; beyond stop-time
 | 
						|
         (+ (- time stop-time) ; how much beyond stop-time
 | 
						|
            start-time
 | 
						|
            (* stretch (- stop-time start-time))))))
 | 
						|
         
 | 
						|
 | 
						|
;; EVENT-STRETCH -- apply time warp to an event
 | 
						|
(defun event-stretch (event stretch dur-flag time-flag start-time stop-time)
 | 
						|
  (let* ((new-time (event-time event))
 | 
						|
         (new-dur (event-dur event))
 | 
						|
         (end-time (+ new-time new-dur)))
 | 
						|
    (cond (time-flag
 | 
						|
           (setf new-time (time-stretch new-time stretch 
 | 
						|
                                        start-time stop-time))))
 | 
						|
    (cond ((and time-flag dur-flag)
 | 
						|
           ;; both time and dur are stretched, so map the end time just
 | 
						|
           ;; like the start time, then subtract to get new duration
 | 
						|
           (setf end-time (time-stretch end-time stretch
 | 
						|
                                        start-time stop-time))
 | 
						|
           (setf new-dur (- end-time new-time)))
 | 
						|
          ((and dur-flag (>= new-time start-time) (< new-time stop-time))
 | 
						|
           ;; stretch only duration, not time. If note starts in range
 | 
						|
           ;; scale to get the new duration.
 | 
						|
           (setf new-dur (* stretch new-dur))))
 | 
						|
    (list new-time new-dur (event-expression event))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-STRETCH -- stretch a region of the score
 | 
						|
;;
 | 
						|
(defun score-stretch (score factor &key (dur t) (time t)
 | 
						|
                      from-index to-index (from-time 0) (to-time FOREVER))
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((begin-end (event-expression (car score)))
 | 
						|
        (i 1))
 | 
						|
    (if from-index
 | 
						|
        (setf from-time (max from-time 
 | 
						|
                             (event-time (nth from-index score)))))
 | 
						|
    (if to-index
 | 
						|
        (setf to-time (min to-time 
 | 
						|
                           (event-end (nth to-index score)))))
 | 
						|
    ; stretch from start-time to stop-time
 | 
						|
    (cons (list 0 0 (list 'SCORE-BEGIN-END 
 | 
						|
                          (time-stretch (cadr begin-end) factor 
 | 
						|
                                        from-time to-time)
 | 
						|
                          (time-stretch (caddr begin-end) factor
 | 
						|
                                        from-time to-time)))
 | 
						|
          (mapcar #'(lambda (event) 
 | 
						|
                      (event-stretch event factor dur time
 | 
						|
                                     from-time to-time))
 | 
						|
                  (cdr score)))))
 | 
						|
    
 | 
						|
          
 | 
						|
(defun params-transpose (params keyword amount)
 | 
						|
  (cond ((null params) nil)
 | 
						|
        ((and (eq keyword (car params))
 | 
						|
              (numberp (cadr params)))
 | 
						|
         (cons (car params)
 | 
						|
               (cons (+ amount (cadr params))
 | 
						|
                     (cddr params))))
 | 
						|
        (t (cons (car params)
 | 
						|
                 (cons (cadr params)
 | 
						|
                       (params-transpose (cddr params) keyword amount))))))
 | 
						|
 | 
						|
 | 
						|
(defun score-transpose (score keyword amount &key
 | 
						|
                        from-index to-index from-time to-time)
 | 
						|
  (score-apply score 
 | 
						|
               #'(lambda (time dur expression)
 | 
						|
                   (list time dur 
 | 
						|
                         (cons (car expression)
 | 
						|
                               (params-transpose (cdr expression)
 | 
						|
                                                 keyword amount))))
 | 
						|
               :from-index from-index :to-index to-index
 | 
						|
               :from-time from-time :to-time to-time))
 | 
						|
 | 
						|
 | 
						|
(defun params-scale (params keyword amount)
 | 
						|
  (cond ((null params) nil)
 | 
						|
        ((and (eq keyword (car params))
 | 
						|
              (numberp (cadr params)))
 | 
						|
         (cons (car params)
 | 
						|
               (cons (* amount (cadr params))
 | 
						|
                     (cddr params))))
 | 
						|
        (t (cons (car params)
 | 
						|
                 (cons (cadr params)
 | 
						|
                       (params-scale (cddr params) keyword amount))))))
 | 
						|
 | 
						|
 | 
						|
(defun score-scale (score keyword amount &key
 | 
						|
                    from-index to-index from-time to-time)
 | 
						|
  (score-apply score 
 | 
						|
               #'(lambda (time dur expression)
 | 
						|
                   (list time dur
 | 
						|
                         (cons (car expression)
 | 
						|
                               (params-scale (cdr expression)
 | 
						|
                                             keyword amount))))
 | 
						|
               :from-index from-index :to-index to-index
 | 
						|
               :from-time from-time :to-time to-time))
 | 
						|
 | 
						|
 | 
						|
(defun score-sustain (score factor &key
 | 
						|
                      from-index to-index from-time to-time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((i 0)
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        result)
 | 
						|
    (dolist (event score)
 | 
						|
      (cond ((and (<= start i) (< i stop))
 | 
						|
             (setf event (event-set-dur
 | 
						|
                          event (* (event-dur event) factor)))))
 | 
						|
      (push event result)
 | 
						|
      (incf i))
 | 
						|
    (reverse result)))
 | 
						|
 | 
						|
 | 
						|
(defun map-voice (expression replacement-list)
 | 
						|
  (let ((mapping (assoc (car expression) replacement-list)))
 | 
						|
    (cond (mapping (cons (second mapping)
 | 
						|
                         (cdr expression)))
 | 
						|
          (t expression))))
 | 
						|
 | 
						|
 | 
						|
(defun score-voice (score replacement-list &key
 | 
						|
                    from-index to-index from-time to-time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((i 0) 
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        result)
 | 
						|
    (dolist (event score)
 | 
						|
      (cond ((and (<= start i) (< i stop))
 | 
						|
             (setf event (event-set-expression
 | 
						|
                          event (map-voice (event-expression event)
 | 
						|
                                           replacement-list)))))
 | 
						|
      (push event result)
 | 
						|
      (incf i))
 | 
						|
    (reverse result)))
 | 
						|
 | 
						|
 | 
						|
(defun score-merge (&rest scores)
 | 
						|
  ;; scores is a list of scores
 | 
						|
  (cond ((null scores) nil)
 | 
						|
        (t
 | 
						|
         (score-merge-1 (car scores) (cdr scores)))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-MERGE-1 -- merge list of scores into score
 | 
						|
;;
 | 
						|
(defun score-merge-1 (score scores)
 | 
						|
  ;; scores is a list of scores to merge
 | 
						|
  (cond ((null scores) score)
 | 
						|
        (t (score-merge-1 (score-merge-2 score (car scores))
 | 
						|
                          (cdr scores)))))
 | 
						|
 | 
						|
;; SCORE-MERGE-2 -- merge 2 scores
 | 
						|
;;
 | 
						|
(defun score-merge-2 (score addin)
 | 
						|
  ;(display "score-merge-2 before" score addin)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (setf addin (score-must-have-begin-end addin))
 | 
						|
  ;(display "score-merge-2" score addin)
 | 
						|
  (let (start1 start2 end1 end2)
 | 
						|
    (setf start1 (score-get-begin score))
 | 
						|
    (setf start2 (score-get-begin addin))
 | 
						|
    (setf end1 (score-get-end score))
 | 
						|
    (setf end2 (score-get-end addin))
 | 
						|
    
 | 
						|
    ;; note: score-sort is destructive, but append copies score
 | 
						|
    ;;       and score-shift copies addin
 | 
						|
    (score-sort
 | 
						|
     (cons (list 0 0 (list 'SCORE-BEGIN-END (min start1 start2)
 | 
						|
                           (max end1 end2)))
 | 
						|
           (append (cdr score) (cdr addin) nil)))))
 | 
						|
 | 
						|
 | 
						|
 | 
						|
;; SCORE-APPEND -- append scores together in sequence
 | 
						|
;;
 | 
						|
(defun score-append (&rest scores)
 | 
						|
  ;; scores is a list of scores
 | 
						|
  (cond ((null scores) nil)
 | 
						|
        (t
 | 
						|
         (score-append-1 (car scores) (cdr scores)))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-APPEND-1 -- append list of scores into score
 | 
						|
;;
 | 
						|
(defun score-append-1 (score scores)
 | 
						|
  ;; scores is a list of scores to append
 | 
						|
  (cond ((null scores) score)
 | 
						|
        (t (score-append-1 (score-append-2 score (car scores))
 | 
						|
                           (cdr scores)))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-APPEND-2 -- append 2 scores
 | 
						|
;;
 | 
						|
(defun score-append-2 (score addin)
 | 
						|
  ;(display "score-append-2" score addin)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (setf addin (score-must-have-begin-end addin))
 | 
						|
  (let (end1 start2 begin-end1 begin-end2)
 | 
						|
    (setf start1 (score-get-begin score))
 | 
						|
    (setf end1 (score-get-end score))
 | 
						|
    (setf start2 (score-get-begin addin))
 | 
						|
    (setf end2 (score-get-end addin))
 | 
						|
    (setf begin-end1 (event-expression (car score)))
 | 
						|
    (setf begin-end2 (event-expression (car addin)))
 | 
						|
    (setf addin (score-shift addin (- end1 start2)))
 | 
						|
    ;; note: score-sort is destructive, but append copies score
 | 
						|
    ;;       and score-shift copies addin
 | 
						|
    (score-sort
 | 
						|
     (cons (list 0 0 (list 'SCORE-BEGIN-END start1 (+ end1 (- end2 start2))))
 | 
						|
           (append (cdr score) (cdr addin) nil)))))
 | 
						|
 | 
						|
 | 
						|
(defun score-select (score predicate &key
 | 
						|
                    from-index to-index from-time to-time reject)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((begin-end (car score))
 | 
						|
        (i 1) 
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        result)
 | 
						|
    ;; selected if start <= i AND i < stop AND predicate(...)
 | 
						|
    ;; choose if not reject and selected or reject and not selected
 | 
						|
    ;; so in other words choose if reject != selected. Use NULL to
 | 
						|
    ;; coerce into boolean values and then use NOT EQ to compare
 | 
						|
    (dolist (event (cdr score))
 | 
						|
      (cond ((not (eq (null reject)
 | 
						|
                      (null (and (<= start i) (< i stop)
 | 
						|
                                 (or (eq predicate t)
 | 
						|
                                     (funcall predicate 
 | 
						|
                                      (event-time event) 
 | 
						|
                                      (event-dur event) 
 | 
						|
                                      (event-expression event)))))))
 | 
						|
             (push event result)))
 | 
						|
      (incf i))
 | 
						|
    (cons begin-end (reverse result))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-FILTER-LENGTH -- remove notes beyond cutoff time
 | 
						|
;;
 | 
						|
(defun score-filter-length (score cutoff)
 | 
						|
  (let (result)
 | 
						|
    (dolist (event score)
 | 
						|
      (cond ((<= (event-end event) cutoff)
 | 
						|
             (push event result))))
 | 
						|
    (reverse result)))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-REPEAT -- make n copies of score in sequence
 | 
						|
;;
 | 
						|
(defun score-repeat (score n)
 | 
						|
  (let (result)
 | 
						|
    (dotimes (i n)
 | 
						|
      (setf result (score-append result score)))
 | 
						|
    result))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-STRETCH-TO-LENGTH -- stretch score to have given length
 | 
						|
;;
 | 
						|
(defun score-stretch-to-length (score length)
 | 
						|
  (let ((begin-time (score-get-begin score))
 | 
						|
        (end-time (score-get-end score))
 | 
						|
        duration stretch)
 | 
						|
    (setf duration (- end-time begin-time))
 | 
						|
    (cond ((< 0 duration)
 | 
						|
           (setf stretch (/ length (- end-time begin-time)))
 | 
						|
           (score-stretch score stretch))
 | 
						|
          (t score))))
 | 
						|
 | 
						|
 | 
						|
(defun score-filter-overlap (score)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (prog (event end-time filtered-score
 | 
						|
         (begin-end (car score)))
 | 
						|
    (setf score (cdr score))
 | 
						|
    (cond ((null score) (return (list begin-end))))
 | 
						|
  loop
 | 
						|
    ;; get event from score
 | 
						|
    (setf event (car score))
 | 
						|
    ;; add a note to filtered-score
 | 
						|
    (push event filtered-score)
 | 
						|
    ;; save the end-time of this event: start + duration
 | 
						|
    (setf end-time (+ (car event) (cadr event)))
 | 
						|
    ;; now skip everything until end-time in score
 | 
						|
  loop2
 | 
						|
    (pop score) ;; move to next event in score
 | 
						|
    (cond ((null score) 
 | 
						|
           (return (cons begin-end (reverse filtered-score)))))
 | 
						|
    (setf event (car score)) ;; examine next event
 | 
						|
    (setf start-time (car event))
 | 
						|
    ;(display "overlap" start-time (- end-time SCORE-EPSILON))
 | 
						|
    (cond ((< start-time (- end-time SCORE-EPSILON))
 | 
						|
           ;(display "toss" event start-time end-time)
 | 
						|
           (go loop2)))
 | 
						|
    (go loop)))
 | 
						|
 | 
						|
 | 
						|
(defun score-print (score)
 | 
						|
 (format t "(")
 | 
						|
 (dolist (event score)
 | 
						|
  (format t "~S~%" event))
 | 
						|
 (format t ")~%"))
 | 
						|
 | 
						|
(defun score-play (score)
 | 
						|
  (play (timed-seq score)))
 | 
						|
 | 
						|
 | 
						|
(defun score-adjacent-events (score function &key
 | 
						|
                              from-index to-index from-time to-time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((begin-end (car score))
 | 
						|
        (a nil)
 | 
						|
        (b (second score))
 | 
						|
        (c-list (cddr score))
 | 
						|
        r newscore
 | 
						|
        (i 1)
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time)))
 | 
						|
    (dolist (event (cdr score))
 | 
						|
      (setf r b)
 | 
						|
      (cond ((and (<= start i) (< i stop))
 | 
						|
             (setf r (funcall function a b (car c-list)))))
 | 
						|
      (cond (r
 | 
						|
             (push r newscore)
 | 
						|
             (setf a r)))
 | 
						|
      (setf b (car c-list))
 | 
						|
      (setf c-list (cdr c-list))
 | 
						|
      (incf i))
 | 
						|
    (score-sort (cons begin-end newscore))))
 | 
						|
 | 
						|
 | 
						|
(defun score-apply (score fn &key
 | 
						|
                    from-index to-index from-time to-time)
 | 
						|
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((begin-end (car score))
 | 
						|
        (i 1) 
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        result)
 | 
						|
    (dolist (event (cdr score))
 | 
						|
      (push 
 | 
						|
       (cond ((and (<= start i) (< i stop))
 | 
						|
              (funcall fn (event-time event)
 | 
						|
                          (event-dur event) (event-expression event)))
 | 
						|
             (t event))
 | 
						|
       result)
 | 
						|
      (incf i))
 | 
						|
    (score-sort (cons begin-end result))))
 | 
						|
 | 
						|
 | 
						|
(defun score-indexof (score fn &key
 | 
						|
                      from-index to-index from-time to-time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((i 1) 
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        result)
 | 
						|
    (dolist (event (cdr score))
 | 
						|
      (cond ((and (<= start i) (< i stop)
 | 
						|
                  (funcall fn (event-time event)
 | 
						|
                              (event-dur event)
 | 
						|
                              (event-expression event)))
 | 
						|
             (setf result i)
 | 
						|
             (return)))
 | 
						|
      (incf i))
 | 
						|
    result))
 | 
						|
 | 
						|
 | 
						|
(defun score-last-indexof (score fn &key
 | 
						|
                           from-index to-index from-time to-time)
 | 
						|
  (setf score (score-must-have-begin-end score))
 | 
						|
  (let ((i 1) 
 | 
						|
        (start (find-first-note score from-index from-time))
 | 
						|
        (stop (find-last-note score to-index to-time))
 | 
						|
        result)
 | 
						|
    (dolist (event (cdr score))
 | 
						|
      (cond ((and (<= start i) (< i stop)
 | 
						|
                  (funcall fn (event-time event)
 | 
						|
                           (event-dur event)
 | 
						|
                           (event-expression event)))
 | 
						|
             (setf result i)))
 | 
						|
      (incf i))
 | 
						|
    result))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-RANDOMIZE-START -- alter start times with offset
 | 
						|
;; keywords: jitter, offset, feel factor
 | 
						|
;;
 | 
						|
(defun score-randomize-start (score amt &key
 | 
						|
                              from-index to-index from-time to-time)
 | 
						|
  (score-apply score
 | 
						|
               (lambda (time dur expr)
 | 
						|
                 (setf time (+ (real-random (- amt) amt) time))
 | 
						|
                 (setf time (max 0.0 time))
 | 
						|
                 (list time dur expr))))
 | 
						|
 | 
						|
 | 
						|
;; SCORE-READ-SMF -- read a standard MIDI file to a score
 | 
						|
;;
 | 
						|
(defun score-read-smf (filename)
 | 
						|
  (let ((seq (seq-create))
 | 
						|
        (file (open-binary filename)))
 | 
						|
    (cond (file
 | 
						|
           (seq-read-smf seq file)
 | 
						|
           (close file)
 | 
						|
           (score-from-seq seq))
 | 
						|
          (t nil))))
 | 
						|
 | 
						|
 | 
						|
;; SET-PROGRAM-TO -- a helper function to set a list value
 | 
						|
(defun set-program-to (lis index value default)
 | 
						|
  ;; if length or lis <= index, extend the lis with default
 | 
						|
  (while (<= (length lis) index)
 | 
						|
    (setf lis (nconc lis (list default))))
 | 
						|
  ;; set the nth element
 | 
						|
  (setf (nth index lis) value)
 | 
						|
  ;; return the list
 | 
						|
  lis)
 | 
						|
 | 
						|
 | 
						|
(defun score-from-seq (seq)
 | 
						|
  (prog (event tag score programs)
 | 
						|
    (seq-reset seq)
 | 
						|
loop
 | 
						|
    (setf event (seq-get seq))
 | 
						|
    (setf tag (seq-tag event))
 | 
						|
    (cond ((= tag seq-done-tag)
 | 
						|
           (go exit))
 | 
						|
          ((= tag seq-prgm-tag)
 | 
						|
           (let ((chan (seq-channel event))
 | 
						|
                 (when (seq-time event))
 | 
						|
                 (program (seq-program event)))
 | 
						|
             (setf programs (set-program-to programs chan program 0))
 | 
						|
             (push (list (* when 0.001) 1
 | 
						|
                         (list 'NOTE :pitch nil :program program))
 | 
						|
                   score)))
 | 
						|
          ((= tag seq-note-tag)
 | 
						|
         (let ((chan (seq-channel event))
 | 
						|
                 (pitch (seq-pitch event))
 | 
						|
                 (vel (seq-velocity event))
 | 
						|
                 (when (seq-time event))
 | 
						|
                 (dur (seq-duration event)))
 | 
						|
             (push (list (* when 0.001) (* dur 0.001)
 | 
						|
                       (list 'NOTE :chan (1- chan) :pitch pitch :vel vel))
 | 
						|
                   score))))
 | 
						|
    (seq-next seq)
 | 
						|
    (go loop)
 | 
						|
exit
 | 
						|
    (setf *rslt* programs) ;; extra return value
 | 
						|
    (return (score-sort score))))
 | 
						|
 | 
						|
 | 
						|
(defun score-write-smf (score filename &optional programs)
 | 
						|
  (let ((file (open-binary filename :direction :output))
 | 
						|
        (seq (seq-create))
 | 
						|
        (chan 1))
 | 
						|
    (cond (file
 | 
						|
           (dolist (program programs)
 | 
						|
             ;; 6 = SEQ_PROGRAM
 | 
						|
             (seq-insert-ctrl seq 0 0 6 chan program)
 | 
						|
             ;(display "insert ctrl" seq 0 0 6 chan program)
 | 
						|
             (incf chan))
 | 
						|
 | 
						|
           (dolist (event (cdr (score-must-have-begin-end score)))
 | 
						|
             (let ((time (event-time event))
 | 
						|
                   (dur (event-dur event))
 | 
						|
                   (chan (event-get-attr event :chan 0))
 | 
						|
                   (pitch (event-get-attr event :pitch))
 | 
						|
                   (program (event-get-attr event :program))
 | 
						|
                   (vel (event-get-attr event :vel 100)))
 | 
						|
               (cond (program
 | 
						|
                      ;(display "score-write-smf program" chan program)
 | 
						|
                      (seq-insert-ctrl seq (round (* time 1000))
 | 
						|
                                       0 6 (1+ chan)
 | 
						|
                                       (round program))))
 | 
						|
               (cond ((consp pitch)
 | 
						|
                      (dolist (p pitch)
 | 
						|
                        (seq-insert-note seq (round (* time 1000))
 | 
						|
                                         0 (1+ chan) (round p) 
 | 
						|
                                         (round (* dur 1000)) (round vel))))
 | 
						|
                     (pitch
 | 
						|
                      (seq-insert-note seq (round (* time 1000))
 | 
						|
                                       0 (1+ chan) (round pitch)
 | 
						|
                                       (round (* dur 1000)) (round vel))))))
 | 
						|
           (seq-write-smf seq file)
 | 
						|
           (close file)))))
 | 
						|
 | 
						|
 | 
						|
;; make a default note function for scores
 | 
						|
;;
 | 
						|
(defun note (&key (pitch 60) (vel 100))
 | 
						|
  ;; load the piano if it is not loaded already
 | 
						|
  (if (not (boundp '*piano-srate*)) 
 | 
						|
      (abs-env (load "pianosyn")))
 | 
						|
  (piano-note-2 pitch vel))
 | 
						|
 | 
						|
;;================================================================
 | 
						|
 | 
						|
;; WORKSPACE functions have moved to envelopes.lsp
 | 
						|
 | 
						|
 | 
						|
;; DESCRIBE -- add a description to a global variable
 | 
						|
;;
 | 
						|
(defun describe (symbol &optional description)
 | 
						|
  (add-to-workspace symbol)
 | 
						|
  (cond (description
 | 
						|
         (putprop symbol description 'description))
 | 
						|
        (t
 | 
						|
         (get symbol 'description))))
 | 
						|
 | 
						|
;; INTERPOLATE -- linear interpolation function
 | 
						|
;;
 | 
						|
;; compute y given x by interpolating between points (x1, y1) and (x2, y2)
 | 
						|
(defun interpolate (x x1 y1 x2 y2)
 | 
						|
  (cond ((= x1 x2) x1)
 | 
						|
        (t (+ y1 (* (- x x1) (/ (- y2 y1) (- x2 (float x1))))))))
 | 
						|
 | 
						|
 | 
						|
;; INTERSECTION -- set intersection
 | 
						|
;;
 | 
						|
;; compute the intersection of two lists
 | 
						|
(defun intersection (a b)
 | 
						|
  (let (result)
 | 
						|
    (dolist (elem a)
 | 
						|
      (if (member elem b) (push elem result)))
 | 
						|
    result))
 | 
						|
 | 
						|
;; UNION -- set union
 | 
						|
;;
 | 
						|
;; compute the union of two lists
 | 
						|
(defun union (a b)
 | 
						|
  (let (result)
 | 
						|
    (dolist (elem a)
 | 
						|
      (if (not (member elem result)) (push elem result)))
 | 
						|
    (dolist (elem b)
 | 
						|
      (if (not (member elem result)) (push elem result)))
 | 
						|
    result))
 | 
						|
 | 
						|
;; SET-DIFFERENCE -- set difference
 | 
						|
;;
 | 
						|
;; compute the set difference between two sets
 | 
						|
(defun set-difference (a b)
 | 
						|
  (remove-if (lambda (elem) (member elem b)) a))
 | 
						|
 | 
						|
;; SUBSETP -- test is list is subset
 | 
						|
;;
 | 
						|
;; test if a is subset of b
 | 
						|
(defun subsetp (a b)
 | 
						|
  (let ((result t))
 | 
						|
    (dolist (elem a)
 | 
						|
      (cond ((not (member elem b))
 | 
						|
             (setf result nil)
 | 
						|
             (return nil))))
 | 
						|
    result))
 | 
						|
 | 
						|
;; functions to support score editing in jNyqIDE
 | 
						|
 | 
						|
(if (not (boundp '*default-score-file*))
 | 
						|
    (setf *default-score-file* "score.dat"))
 | 
						|
 | 
						|
;; SCORE-EDIT -- save a score for editing by jNyqIDE
 | 
						|
;;
 | 
						|
;; file goes to a data file to be read by jNyqIDE
 | 
						|
;; Note that the parameter is a global variable name, not a score,
 | 
						|
;; but you do not quote the global variable name, e.g. call
 | 
						|
;;    (score-edit my-score)
 | 
						|
;;
 | 
						|
(defmacro score-edit (score-name)
 | 
						|
    `(score-edit-symbol (quote ,score-name)))
 | 
						|
 | 
						|
(defun score-edit-symbol (score-name)
 | 
						|
    (prog ((f (open *default-score-file* :direction :output))
 | 
						|
           score expr)
 | 
						|
      (cond ((symbolp score-name)
 | 
						|
             (setf score (eval score-name)))
 | 
						|
            (t
 | 
						|
             (error "score-edit expects a symbol naming the score to edit")))
 | 
						|
      (cond ((null f)
 | 
						|
        (format t "score-edit: error in output file ~A!~%" *default-score-file*)
 | 
						|
        (return nil)))
 | 
						|
 | 
						|
      (format t "score-edit: writing ~A ...~%" *default-score-file*)
 | 
						|
      (format f "~A~%" score-name) ; put name on first line
 | 
						|
      (dolist (event score) ;cdr scor
 | 
						|
        (format f "~A " (event-time event))  ; print start time
 | 
						|
        (format f "~A " (event-dur event))   ; print duration
 | 
						|
 | 
						|
        (setf expr (event-expression event))
 | 
						|
 | 
						|
        ; print the pitch and the rest of the attributes
 | 
						|
        (format f "~A " (expr-get-attr expr :pitch))
 | 
						|
        (format f "~A~%" (expr-parameters-remove-attr expr :pitch)))
 | 
						|
      (close f)
 | 
						|
      (format t "score-edit: wrote ~A events~%" (length score))))
 | 
						|
 | 
						|
 | 
						|
;; Read in a data file stored in the score-edit format and save
 | 
						|
;; it to the global variable it came from
 | 
						|
(defun score-restore ()
 | 
						|
  (prog ((inf (open *default-score-file*))
 | 
						|
         name start dur pitch expr score)
 | 
						|
    (cond ((null inf)
 | 
						|
           (format t "score-restore: could not open ~A~%" *default-score-file*)
 | 
						|
           (return nil)))
 | 
						|
    (setf name (read inf)) ;; score name
 | 
						|
    (loop
 | 
						|
      (setf start (read inf))
 | 
						|
      (cond ((null start) (return)))
 | 
						|
      (setf dur (read inf))
 | 
						|
      (setf pitch (read inf))
 | 
						|
      (setf expr (read inf))
 | 
						|
      (cond (pitch
 | 
						|
             (setf expr (expr-set-attr expr :pitch pitch)))))
 | 
						|
    (close inf)
 | 
						|
    (setf (symbol-value name) score)))
 |