d@74
|
1 (in-package "AMUSE-SEGMENTATION")
|
d@74
|
2
|
d@74
|
3 ;;;;;;;;;;;;;;;;;
|
d@74
|
4 ;;
|
d@74
|
5 ;; Demo example: `simple segmenter'
|
d@74
|
6 ;;
|
d@74
|
7 ;; Simple segmenter segments a piece at long gaps between notes (where
|
d@74
|
8 ;; `gaps' are the amount of time between successive note-onsets).
|
d@74
|
9 ;;
|
d@74
|
10 ;; In this implementation, the modal gap-length is calculated (the
|
d@74
|
11 ;; highest is taken in cases of a tie) and a threshold is taken of 3.9
|
d@74
|
12 ;; times that length. An absolute gap of 1.5 seconds is also used if
|
d@74
|
13 ;; beats-to-seconds is available.
|
d@74
|
14
|
m@78
|
15 (defclass simple-segmenter (after-segmenter) ())
|
d@75
|
16
|
d@136
|
17 (defmethod boundary-strength ((segmenter simple-segmenter) (event moment) (composition composition))
|
d@75
|
18 (declare (ignore segmenter))
|
d@75
|
19 (multiple-value-bind (i-o-i mode i-o-i-rt)
|
d@75
|
20 (inter-onset-intervals-for-simple-segmenter-with-cache composition)
|
d@75
|
21 (if (or (eq event (elt composition 0))
|
d@75
|
22 (> (cdr (assoc event i-o-i)) (* mode 3.9))
|
d@75
|
23 (and i-o-i-rt (> (cdr (assoc event i-o-i-rt))
|
d@75
|
24 1.5)))
|
d@75
|
25 1
|
d@75
|
26 0)))
|
d@74
|
27
|
d@74
|
28 ;; helper-functions
|
d@74
|
29
|
d@74
|
30 (defparameter *i-o-i-cache* (make-hash-table :weakness :KEY))
|
d@74
|
31 (defun inter-onset-intervals-for-simple-segmenter-with-cache (composition &key (rounding-divisor 1/4))
|
d@74
|
32 (unless (gethash composition *i-o-i-cache*)
|
d@74
|
33 (setf (gethash composition *i-o-i-cache*)
|
d@74
|
34 (multiple-value-list (inter-onset-intervals-for-simple-segmenter composition
|
d@74
|
35 :rounding-divisor rounding-divisor))))
|
d@74
|
36 (values-list (gethash composition *i-o-i-cache*)))
|
d@74
|
37
|
d@74
|
38 (defgeneric inter-onset-intervals-for-simple-segmenter (composition &key rounding-divisor)
|
d@74
|
39 (:documentation "Returns values: inter-onset-intervals, i-o-i
|
d@74
|
40 mode and real-time-i-o-is. Both sets of i-o-is are alists
|
d@74
|
41 of (<event> . i-o-i)"))
|
m@78
|
42
|
d@74
|
43 (defmethod inter-onset-intervals-for-simple-segmenter ((composition composition)
|
d@74
|
44 &key (rounding-divisor 1/4))
|
d@74
|
45 ;; FIXME: (vaguely) assumes monody
|
d@74
|
46 (let ((i-o-i-list) (i-o-i-secs-list) (real-time t) (prev) (i-o-i-period)
|
d@77
|
47 (hits (make-hash-table))
|
d@74
|
48 (modal-value) (modal-count 0))
|
d@74
|
49 (sequence:dosequence (event composition)
|
d@74
|
50 (when prev
|
d@74
|
51 (setf i-o-i-period (inter-onset-interval prev event))
|
d@74
|
52 (when real-time
|
d@74
|
53 (setf i-o-i-secs-list
|
d@74
|
54 (acons event (handler-case (beats-to-seconds i-o-i-period composition)
|
d@74
|
55 ((or undefined-action insufficient-information) ()
|
d@74
|
56 (setf real-time nil)))
|
d@74
|
57 i-o-i-secs-list)))
|
d@74
|
58 (setf i-o-i-list (acons event (duration i-o-i-period) i-o-i-list))
|
d@77
|
59 (when (> (round (duration i-o-i-period) rounding-divisor) 0)
|
d@74
|
60 ;; larger than 32 crotchets doesn't really count as a useful
|
d@74
|
61 ;; i-o-i for our purposes (or am I just doing this because
|
d@77
|
62 ;; I'm using an array).
|
m@78
|
63 (if (gethash (round (duration i-o-i-period) rounding-divisor) hits)
|
m@78
|
64 (incf (gethash (round (duration i-o-i-period) rounding-divisor) hits))
|
m@78
|
65 (setf (gethash (round (duration i-o-i-period) rounding-divisor) hits) 1))))
|
d@74
|
66 (setf prev event))
|
m@78
|
67 ;; we want the highest mode if there are several
|
m@78
|
68 (maphash #'(lambda (key value)
|
m@78
|
69 (when (> value modal-count)
|
m@78
|
70 (setf modal-value key
|
m@78
|
71 modal-count value)))
|
m@78
|
72 hits)
|
d@74
|
73 (values i-o-i-list (* modal-value rounding-divisor) (and real-time i-o-i-secs-list))))
|