d@33
|
1 (in-package #:amuse-harmony)
|
d@33
|
2
|
d@33
|
3 ;; This file contains functions for performing harmonic analysis and
|
d@33
|
4 ;; chord labelling. At the moment it's quite crude.
|
d@33
|
5 ;;
|
d@33
|
6 ;; Probability can be estimated based on a function that must take a
|
d@33
|
7 ;; window on the music (i.e. an anchored period and a composition (?
|
d@33
|
8 ;; or perhaps a 'constituent' in future?). The functionality below is
|
d@33
|
9 ;; a cut-down version of its predecessors and only models one pitch
|
d@33
|
10 ;; model, derived by combining dirichlet distributions on the local
|
d@33
|
11 ;; distribution of pitch-class durations in terms of
|
d@33
|
12 ;; chord-note:non-chord-note ratios and relative weighting of chord
|
d@33
|
13 ;; notes.
|
d@33
|
14 ;;
|
d@33
|
15 ;; * Chord objects contain details of chord types including the
|
d@33
|
16 ;; intervals of their constituents and any putative distributional
|
d@33
|
17 ;; information or note profiles or templates.
|
d@33
|
18 ;;
|
d@33
|
19 ;; * Chordset objects gather chord-types together for a given
|
d@33
|
20 ;; experiment. They have a slot for priors for historical reasons, but
|
d@33
|
21 ;; at the moment this is unused - I'm using other structures for this.
|
d@33
|
22 ;;
|
d@33
|
23 ;; * likelihoods are currently alists with a host of methods. (FIXME:
|
d@33
|
24 ;; this doesn't seem very clever)
|
d@33
|
25 ;;
|
d@33
|
26
|
d@33
|
27 ;; FIXME: this is in the wrong place
|
d@34
|
28 (defparameter *keys* (make-array 12 :initial-contents '(:c :c# :d :eb :e :f :f# :g :ab :a :bb :b)))
|
d@33
|
29
|
d@33
|
30 (defparameter *path-options*
|
d@33
|
31 ;; Each of these is a set of division-of-the-bar options for each
|
d@33
|
32 ;; metrical type.
|
d@33
|
33 ;;
|
d@33
|
34 ;; FIXME: behaviour if the time-signature numerator is absent from
|
d@33
|
35 ;; the alist is undefined.
|
d@33
|
36 '((4 (1 1 1 1) (1 1 2) (1 2 1) (1 3)
|
d@33
|
37 (2 1 1) (2 2) (3 1) (4))
|
d@33
|
38 ;; (4 (1 1 1 1) (1 1 2) (2 1 1) (2 2) (4))
|
d@33
|
39 (2 (1 1) (2))
|
d@33
|
40 (3 (1 1 1) (1 2) (2 1) (3))
|
d@33
|
41 (6 (3 3) (6))
|
d@33
|
42 (5 (1 1 1 1 1) (1 1 1 2) (1 1 2 1) (1 1 3)
|
d@33
|
43 (1 2 1 1) (1 2 2) (1 3 1) (1 4)
|
d@33
|
44 (2 1 1 1) (2 1 2) (2 2 1) (2 3)
|
d@33
|
45 (3 1 1) (3 2) (4 1) (5))
|
d@33
|
46 (9 (3 3 3) (3 6) (6 3) (9))
|
d@33
|
47 (12 (3 3 3 3) (3 3 6) (3 6 3) (3 9)
|
d@33
|
48 (6 3 3) (6 6) (9 3) (12))))
|
d@33
|
49
|
d@33
|
50 #+nil
|
d@33
|
51 (defparameter *default-models* '(:constant-prior :gamma))
|
d@33
|
52 #+nil
|
d@33
|
53 (defparameter *default-models* '(:scaled-prior :gamma :naive-bass))
|
d@33
|
54 ;; #+nil
|
d@33
|
55 (defparameter *default-models* '(:scaled-prior :gamma))
|
d@33
|
56 #+nil
|
d@33
|
57 (defparameter *default-models* '(:scaled-prior :gamma :metrical-prior))
|
d@33
|
58
|
d@33
|
59 ;;; ACCESSORS
|
d@33
|
60 ;; Nearly empty now. And not much point in what's left
|
d@33
|
61 (defgeneric normalised-distribution (chord &optional total))
|
d@33
|
62 (defmethod normalised-distribution ((chord chord) &optional (total 1))
|
d@33
|
63 ;; normalised distributions will be reused, so it makes sense to
|
d@33
|
64 ;; store them.
|
d@33
|
65 ;; FIXME: Are these ever going to be useful again?
|
d@33
|
66 (unless (assoc total (slot-value chord 'normalised-distribution))
|
d@33
|
67 (setf (slot-value chord 'normalised-distribution)
|
d@33
|
68 (acons total (normalise-vector (slot-value chord 'distribution) total)
|
d@33
|
69 (slot-value chord 'normalised-distribution))))
|
d@33
|
70 (cdr (assoc total (slot-value chord 'normalised-distribution))))
|
d@33
|
71
|
d@33
|
72 ;;; Object definitions
|
d@33
|
73
|
d@33
|
74 #+nil
|
d@33
|
75 (defparameter *major-ratios* (mapcar (lambda (x) (/ x 217))
|
d@33
|
76 '(180 1 1 1 20 1 1 8 1 1 1 1)))
|
d@33
|
77 #+nil
|
d@33
|
78 (defparameter *major-ratios* (mapcar (lambda (x) (/ x 20))
|
d@33
|
79 '(5 1 2 1 3 1 1 2 1 1 1 1)))
|
d@33
|
80 (defparameter *major-ratios* #(0.72 0.02 0.02 0.02 0.08 0.02 0.02 0.02 0.02 0.02 0.02 0.02))
|
d@33
|
81 #+nil
|
d@33
|
82 (defparameter *minor-ratios* (mapcar (lambda (x) (/ x 302))
|
d@33
|
83 '(280 1 1 4 1 1 1 9 1 1 1 1)))
|
d@33
|
84 (defparameter *minor-ratios* #(0.78 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02))
|
d@33
|
85 #+nil
|
d@33
|
86 (defparameter *minor-ratios* (mapcar (lambda (x) (/ x 20))
|
d@33
|
87 '(7 1 2 2 1 1 1 1 1 1 1 1)))
|
d@33
|
88 #+nil
|
d@33
|
89 (defparameter *sus-ratios* (mapcar (lambda (x) (/ x 21))
|
d@33
|
90 '(10 1 1 1 1 1 1 1 1 1 1 1)))
|
d@33
|
91 (defparameter *sus-ratios* #(0.78 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02))
|
d@33
|
92
|
d@33
|
93 (defparameter *dim-ratios* (copy-seq *sus-ratios*))
|
d@33
|
94 (defparameter *aug-ratios* (copy-seq *sus-ratios*))
|
d@33
|
95
|
d@33
|
96 ;; Chords
|
d@33
|
97 (defparameter *major-chord*
|
d@33
|
98 (make-instance 'chord
|
d@33
|
99 :label :major
|
d@33
|
100 :notes '(0 4 7)
|
d@33
|
101 :bass-likelihoods (make-array 12
|
d@33
|
102 :initial-contents *major-ratios*)
|
d@33
|
103 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
|
d@33
|
104 :distribution (make-array 12
|
d@33
|
105 :initial-contents '(6 1 2 1 5 2 1 5 1 2 2 2))))
|
d@33
|
106
|
d@33
|
107 (defparameter *minor-chord*
|
d@33
|
108 (make-instance 'chord :label :minor :notes '(0 3 7)
|
d@33
|
109 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
|
d@33
|
110 :bass-likelihoods (make-array 12
|
d@33
|
111 :initial-contents *minor-ratios*)
|
d@33
|
112 :distribution (make-array 12
|
d@33
|
113 :initial-contents '(6 1 2 5 1 2 1 5 2 1 2 1))))
|
d@33
|
114
|
d@33
|
115 (defparameter *diminished-chord*
|
d@33
|
116 (make-instance 'chord :label :dim :notes '(0 3 6)
|
d@33
|
117 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
|
d@33
|
118 :bass-likelihoods (make-array 12
|
d@33
|
119 :initial-contents *dim-ratios*)
|
d@33
|
120 :distribution (make-array 12
|
d@33
|
121 :initial-contents '(6 1 1 5 1 1 5 1 1 4 1 1))))
|
d@33
|
122
|
d@33
|
123 (defparameter *diminished-chord-short*
|
d@33
|
124 (make-instance 'chord :label :dim :notes '(0 3 6 9)
|
d@33
|
125 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
|
d@33
|
126 :distribution (make-array 3
|
d@33
|
127 :initial-contents '(6 1 1))))
|
d@33
|
128
|
d@33
|
129 (defparameter *augmented-chord*
|
d@33
|
130 (make-instance 'chord :label :aug :notes '(0 4 8) :min-distribution #(35 25 20 20)
|
d@33
|
131 :bass-likelihoods (make-array 12
|
d@33
|
132 :initial-contents *aug-ratios*)
|
d@33
|
133 :distribution (make-array 12
|
d@33
|
134 :initial-contents '(6 1 1 1 5 1 1 1 5 1 1 1))))
|
d@33
|
135
|
d@33
|
136 (defparameter *augmented-chord-short*
|
d@33
|
137 (make-instance 'chord :label :aug :notes '(0 4 8)
|
d@33
|
138 :distribution (make-array 4
|
d@33
|
139 :initial-contents '(6 1 1 1))))
|
d@33
|
140
|
d@33
|
141 (defparameter *suspended4th-chord*
|
d@33
|
142 (make-instance 'chord :label :sus4 :notes '(0 5 7)
|
d@33
|
143 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
|
d@33
|
144 :bass-likelihoods (make-array 12
|
d@33
|
145 :initial-contents *sus-ratios*)
|
d@33
|
146 :distribution (make-array 12
|
d@33
|
147 :initial-contents '(6 1 2 3 3 5 1 5 1 1 2 2))))
|
d@33
|
148
|
d@33
|
149 (defparameter *suspended9th-chord*
|
d@33
|
150 (make-instance 'chord :label :sus9 :notes '(0 2 7)
|
d@33
|
151 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
|
d@33
|
152 :bass-likelihoods (make-array 12
|
d@33
|
153 :initial-contents *sus-ratios*)
|
d@33
|
154 :distribution (make-array 12
|
d@33
|
155 :initial-contents '(6 1 5 2 2 2 1 5 1 2 2 1))))
|
d@33
|
156
|
d@33
|
157 ;; CHORDSETS
|
d@33
|
158 (defparameter *full-set*
|
d@33
|
159 (make-instance 'chordset :chords (list *major-chord* *minor-chord*
|
d@33
|
160 *diminished-chord* *augmented-chord*
|
d@33
|
161 *suspended4th-chord* *suspended9th-chord*)))
|
d@33
|
162
|
d@33
|
163 (defparameter *full-set-variable-length*
|
d@33
|
164 (make-instance 'chordset :chords (list *major-chord* *minor-chord*
|
d@33
|
165 *diminished-chord-short* *augmented-chord-short*
|
d@33
|
166 *suspended4th-chord* *suspended9th-chord*)))
|
d@33
|
167
|
d@33
|
168 (defparameter *partial-set*
|
d@33
|
169 (make-instance 'chordset :chords (list *major-chord* *minor-chord*
|
d@33
|
170 *diminished-chord* *augmented-chord*)))
|
d@33
|
171
|
d@33
|
172 (defparameter *partial-set-variable-length*
|
d@33
|
173 (make-instance 'chordset
|
d@33
|
174 :chords (list *major-chord* *minor-chord*
|
d@33
|
175 *diminished-chord-short*
|
d@33
|
176 *augmented-chord-short*)))
|
d@33
|
177
|
d@33
|
178 (defparameter *minimal-set*
|
d@33
|
179 (make-instance 'chordset :chords (list *major-chord* *minor-chord*)))
|
d@33
|
180
|
d@33
|
181 #+nil
|
d@33
|
182 (defparameter *chord-proportions* ;; guess
|
d@33
|
183 (list (cons *major-chord* 17/30) (cons *minor-chord* 10/30)
|
d@33
|
184 (cons *diminished-chord* 1/60) (cons *augmented-chord* 1/60)
|
d@33
|
185 (cons *suspended4th-chord* 1/30) (cons *suspended9th-chord* 1/30)))
|
d@33
|
186
|
d@33
|
187 #+nil
|
d@33
|
188 (defparameter *chord-proportions* ;; another guess
|
d@33
|
189 (list (cons *major-chord* 1/3) (cons *minor-chord* 1/3)
|
d@33
|
190 (cons *diminished-chord* 1/30) (cons *augmented-chord* 1/60)
|
d@33
|
191 (cons *suspended4th-chord* 1/5) (cons *suspended9th-chord* 1/12)))
|
d@33
|
192 #+nil
|
d@33
|
193 (defparameter *chord-proportions* ;; flat
|
d@33
|
194 (list (cons *major-chord* 1/6) (cons *minor-chord* 1/6)
|
d@33
|
195 (cons *diminished-chord* 1/6) (cons *augmented-chord* 1/6)
|
d@33
|
196 (cons *suspended4th-chord* 1/6) (cons *suspended9th-chord* 1/6)))
|
d@33
|
197
|
d@33
|
198 (defparameter *chord-proportions*
|
d@33
|
199 ;; observed
|
d@33
|
200 ;; FIXME: This seriously impairs dim and aug. Do they ever get
|
d@33
|
201 ;; diagnosed now?
|
d@33
|
202 (list (cons *major-chord* 546/917) (cons *minor-chord* 312/917)
|
d@33
|
203 (cons *diminished-chord* 2/917) (cons *augmented-chord* 1/917)
|
d@33
|
204 (cons *suspended4th-chord* 44/917) (cons *suspended9th-chord* 12/917)))
|
d@33
|
205
|
d@33
|
206
|
d@33
|
207 ;; First steps to chord labelling
|
d@33
|
208 (defun get-chord-likelihoods-for-model (anchored-period music
|
d@33
|
209 &key (model :gamma)
|
d@33
|
210 (chordset *full-set*))
|
d@33
|
211 ;; Currently expects and returns an alist of (identifier
|
d@33
|
212 ;; . likelihood) (unnormalised, but can use normalise-likelihoods)
|
d@33
|
213 (ecase model
|
d@33
|
214 (:constant-prior
|
d@33
|
215 ;; results are divided by number of chords in chordset (times 12)
|
d@33
|
216 (constant-prior-likelihoods anchored-period
|
d@33
|
217 music
|
d@33
|
218 chordset))
|
d@33
|
219 (:scaled-prior
|
d@33
|
220 ;; results are divided by preset chord weightings (times 12)
|
d@33
|
221 (scaled-prior-likelihoods anchored-period
|
d@33
|
222 music
|
d@33
|
223 chordset))
|
d@33
|
224 (:naive-bass
|
d@33
|
225 (naive-bass-prior-likelihoods anchored-period
|
d@33
|
226 music
|
d@33
|
227 chordset))
|
d@33
|
228 (:metrical-prior
|
d@33
|
229 (metrical-prior-likelihoods anchored-period
|
d@33
|
230 music
|
d@33
|
231 chordset))
|
d@33
|
232 (:gamma
|
d@33
|
233 ;; dirichlet-based likelihood calculation
|
d@33
|
234 (3ple-gamma-likelihoods anchored-period
|
d@33
|
235 music
|
d@33
|
236 chordset))))
|
d@33
|
237
|
d@33
|
238 ;; LIKELIHOOD-CALCULATION FUNCTIONS
|
d@33
|
239 (defgeneric metrical-prior-likelihoods (anchored-period music chordset))
|
d@33
|
240 (defmethod metrical-prior-likelihoods ((anchored-period anchored-period)
|
d@33
|
241 music chordset)
|
d@33
|
242 (let* ((metrical-level (metrical-level-for-likelihood anchored-period music))
|
d@33
|
243 (p (if (= metrical-level 1)
|
d@33
|
244 0.51
|
d@33
|
245 0.07)))
|
d@33
|
246 (loop for chord in (chords chordset)
|
d@33
|
247 nconc (loop for i from 0 to 11
|
d@33
|
248 collect (cons (list i chord)
|
d@33
|
249 (/ p (* 12 (length (chords chordset)))))))))
|
d@33
|
250
|
d@33
|
251 (defgeneric constant-prior-likelihoods (anchored-period music chordset))
|
d@33
|
252 (defmethod constant-prior-likelihoods ((anchored-period anchored-period)
|
d@33
|
253 music chordset)
|
d@33
|
254 ;; returns a flat distribution totalling 1
|
d@33
|
255 (loop for chord in (chords chordset)
|
d@33
|
256 nconc (loop for i from 0 to 11
|
d@33
|
257 collect (cons (list i chord)
|
d@33
|
258 (/ 1 (* 12 (length
|
d@33
|
259 (chords chordset))))))))
|
d@33
|
260
|
d@33
|
261 (defgeneric scaled-prior-likelihoods (anchored-period music chordset &key prior-alist))
|
d@33
|
262 (defmethod scaled-prior-likelihoods ((anchored-period anchored-period) music chordset
|
d@33
|
263 &key (prior-alist *chord-proportions*))
|
d@33
|
264 ;; returns a distribution based on the relative likelihood of chord types
|
d@33
|
265 (loop for chord in (chords chordset)
|
d@33
|
266 nconc (loop for i from 0 to 11
|
d@33
|
267 collect (cons (list i chord)
|
d@33
|
268 (/ (cdr (assoc chord prior-alist))
|
d@33
|
269 12)))))
|
d@33
|
270
|
d@33
|
271 (defgeneric naive-bass-prior-likelihoods (anchored-period music chordset))
|
d@33
|
272 (defmethod naive-bass-prior-likelihoods ((anchored-period anchored-period) music chordset)
|
d@33
|
273 (let ((pc (bass-note anchored-period music)))
|
d@33
|
274 (loop for chord in (chords chordset)
|
d@33
|
275 nconc (loop for i from 0 to 11
|
d@33
|
276 collect (cons (list i chord)
|
d@33
|
277 (/ (aref (bass-likelihoods chord)
|
d@33
|
278 (mod (+ i pc) 12))
|
d@33
|
279 (length (chords chordset))))))))
|
d@33
|
280
|
d@33
|
281 (defgeneric 3ple-gamma-likelihoods (anchored-period music chordset))
|
d@33
|
282 (defmethod 3ple-gamma-likelihoods ((anchored-period anchored-period) music chordset)
|
d@33
|
283 ;; Ask Christophe about what this one does - this function just
|
d@33
|
284 ;; provides data to his dirichlet likelihood functions. Currently
|
d@33
|
285 ;; limited to triads, this has two distributions for relative
|
d@33
|
286 ;; strengths of chord notes and for the relation between chord and
|
d@33
|
287 ;; non-chord notes.
|
d@33
|
288 (let ((pitch-classes (normalised-pitch-class-distribution anchored-period music))
|
d@33
|
289 (metrical-level (metrical-level-for-likelihood anchored-period music))
|
d@33
|
290 (likelihoods))
|
d@33
|
291 (dolist (chord (chords chordset) likelihoods)
|
d@33
|
292 (let ((chord-likelihoods (subseq (min-distribution chord) 0 3))
|
d@33
|
293 (non-chord (aref (min-distribution chord) 3)))
|
d@33
|
294 (loop for offset from 0 to 11
|
d@33
|
295 do (setf likelihoods
|
d@33
|
296 (set-likelihood
|
d@33
|
297 likelihoods chord offset
|
d@33
|
298 (3ple-likelihood (chromatic-rotate pitch-classes (- offset))
|
d@33
|
299 chord-likelihoods
|
d@33
|
300 non-chord
|
d@33
|
301 (main-notes chord)
|
d@33
|
302 metrical-level 1
|
d@33
|
303 (get-alphas chord metrical-level :version :learned)
|
d@33
|
304 (get-betas chord metrical-level :version :learned)))))))))
|
d@33
|
305
|
d@33
|
306 #+nil
|
d@33
|
307 (defun get-alphas (chord metrical-level &key (version :map))
|
d@33
|
308 ;; MP values, map commented
|
d@33
|
309 (cond
|
d@33
|
310 ((or (eq *major-chord* chord)
|
d@33
|
311 (eq *minor-chord* chord))
|
d@33
|
312 (cond
|
d@33
|
313 ((< metrical-level 1)
|
d@33
|
314 (case version
|
d@33
|
315 (:map #(3.7812 2.4955 2.1525))
|
d@33
|
316 (:ml #(4.0398 2.6624 2.2942))
|
d@33
|
317 (:learned #(2.0475 1.365 1.1374999))))
|
d@33
|
318 (t
|
d@33
|
319 (case version
|
d@33
|
320 (:map #(3.6626 1.5234 2.3395))
|
d@33
|
321 (:ml #(3.9119 1.6193 2.4955))
|
d@33
|
322 (:learned #(2.0475 1.365 1.1374999))))))
|
d@33
|
323 (t
|
d@33
|
324 (case version
|
d@33
|
325 (:map #(3.5110 2.0252 1.2963))
|
d@33
|
326 (:ml #(4.0822 2.3459 1.4874))
|
d@33
|
327 (:learned #(2.0475 1.365 1.1374999))))))
|
d@33
|
328
|
d@33
|
329 ;; New, corrected ground truth
|
d@33
|
330 (defun get-alphas (chord metrical-level &key (version :map))
|
d@33
|
331 ;; MP values, map commented
|
d@33
|
332 (cond
|
d@33
|
333 ((or (eq *major-chord* chord)
|
d@33
|
334 (eq *minor-chord* chord))
|
d@33
|
335 (cond
|
d@33
|
336 ((< metrical-level 1)
|
d@33
|
337 (case version
|
d@33
|
338 (:map #(3.7397 2.4923 2.0187))
|
d@33
|
339 (:ml #(3.9434 2.6253 2.1239))
|
d@33
|
340 (:learned #(2.0475 1.365 1.1374999))))
|
d@33
|
341 (t
|
d@33
|
342 (case version
|
d@33
|
343 (:map #(3.2620 1.3882 2.2542))
|
d@33
|
344 (:ml #(3.5200 1.4889 2.4293))
|
d@33
|
345 (:learned #(2.0475 1.365 1.1374999))))))
|
d@33
|
346 (t
|
d@33
|
347 (case version
|
d@33
|
348 (:map #(3.1963 1.8187 1.3340))
|
d@33
|
349 (:ml #(3.6371 2.0621 1.2799))
|
d@33
|
350 (:learned #(2.0475 1.365 1.1374999))))))
|
d@33
|
351
|
d@33
|
352 #+nil
|
d@33
|
353 (defun get-betas (chord metrical-level &key (version :map))
|
d@33
|
354 (cond
|
d@33
|
355 ((eq version :learned)
|
d@33
|
356 (cond
|
d@33
|
357 ((> metrical-level 1/2)
|
d@33
|
358 #(0.97 12))
|
d@33
|
359 ((= metrical-level 1/2)
|
d@33
|
360 #(0.97 6))
|
d@33
|
361 (t #(0.97 4))))
|
d@33
|
362 ((or (eq *major-chord* chord)
|
d@33
|
363 (eq *minor-chord* chord))
|
d@33
|
364 (if (< metrical-level 1)
|
d@33
|
365 (if (eq version :map)
|
d@33
|
366 #(0.6987 3.1724)
|
d@33
|
367 #(0.7164 3.2640))
|
d@33
|
368 (if (eq version :map)
|
d@33
|
369 #(1.3677 5.9215)
|
d@33
|
370 #(1.4454 6.2843))))
|
d@33
|
371 (t
|
d@33
|
372 (if (eq version :map)
|
d@33
|
373 #(0.9358 5.2212)
|
d@33
|
374 #(1.0431 5.8530)))))
|
d@33
|
375
|
d@33
|
376 ;; With new, corrected ground truth
|
d@33
|
377 (defun get-betas (chord metrical-level &key (version :map))
|
d@33
|
378 (cond
|
d@33
|
379 ((eq version :learned)
|
d@33
|
380 (cond
|
d@33
|
381 ((> metrical-level 1/2)
|
d@33
|
382 #(0.97 12))
|
d@33
|
383 ((= metrical-level 1/2)
|
d@33
|
384 #(0.97 6))
|
d@33
|
385 (t #(0.97 4))))
|
d@33
|
386 ((or (eq *major-chord* chord)
|
d@33
|
387 (eq *minor-chord* chord))
|
d@33
|
388 (if (< metrical-level 1)
|
d@33
|
389 (if (eq version :map)
|
d@33
|
390 #(0.7041 3.3448)
|
d@33
|
391 #(0.7190 3.4260))
|
d@33
|
392 (if (eq version :map)
|
d@33
|
393 #(1.3838 6.4581)
|
d@33
|
394 #(1.4872 6.9785))))
|
d@33
|
395 (t
|
d@33
|
396 (if (eq version :map)
|
d@33
|
397 #(0.9558 5.0847)
|
d@33
|
398 #(1.0551 5.6740)))))
|
d@33
|
399
|
d@33
|
400
|
d@33
|
401 (defun chromatic-rotate (vector offset)
|
d@33
|
402 ;; transpose an n-member (chromatic) vector by an integral number of
|
d@33
|
403 ;; steps (semitones)
|
d@33
|
404 (let* ((size (length vector))
|
d@33
|
405 (result (make-array size)))
|
d@33
|
406 (dotimes (i size result)
|
d@33
|
407 (setf (aref result i) (aref vector (mod (- i offset) size))))))
|
d@33
|
408
|
d@33
|
409 (defgeneric metrical-level-for-likelihood (anchored-period music))
|
d@33
|
410 (defmethod metrical-level-for-likelihood (anchored-period (music composition))
|
d@33
|
411 ;; metrical level is a function of time signature and window size
|
d@33
|
412 ;; and is used to modify the gamma function.
|
d@33
|
413 (let ((time-sigs (get-applicable-time-signatures anchored-period music)))
|
d@33
|
414 (cond
|
d@33
|
415 ((= (length time-sigs) 1)
|
d@33
|
416 (/ (duration anchored-period)
|
d@33
|
417 (crotchets-in-a-bar (first time-sigs))))
|
d@33
|
418 ((null time-sigs)
|
d@33
|
419 ;; If, for some reason, we have no time-signature, midi specs
|
d@33
|
420 ;; say assume 4/4.
|
d@33
|
421 (/ (duration anchored-period) 4))
|
d@33
|
422 (t
|
d@33
|
423 (loop for sig in time-sigs
|
d@33
|
424 sum (/ (duration (period-intersection sig
|
d@33
|
425 anchored-period))
|
d@33
|
426 (crotchets-in-a-bar sig)))))))
|
d@33
|
427
|
d@33
|
428 ;;;;;;;;;;;;;;;;;;;;;;;;
|
d@33
|
429 ;;
|
d@33
|
430 ;; Hypothesis comparison / level navigation
|
d@33
|
431 ;;
|
d@33
|
432
|
d@33
|
433 (defun chord-labels (anchored-period music
|
d@33
|
434 &key (chordset *full-set*)
|
d@33
|
435 (models *default-models*))
|
d@33
|
436 (let ((harmonic-analysis (best-level anchored-period music :chordset chordset :models models))
|
d@33
|
437 (best-likelihood) (chord-labels))
|
d@33
|
438 (do ((path (first harmonic-analysis) (cdr path))
|
d@33
|
439 (likelihoods (second harmonic-analysis) (cdr likelihoods)))
|
d@33
|
440 ((null path) (reverse chord-labels))
|
d@33
|
441 (dolist (likelihood (car likelihoods))
|
d@33
|
442 (when (or (null best-likelihood)
|
d@33
|
443 (> (likelihood-likelihood likelihood)
|
d@33
|
444 (likelihood-likelihood best-likelihood)))
|
d@33
|
445 (setf best-likelihood likelihood)))
|
d@33
|
446 (push (cons (first path) (likelihood-chord best-likelihood)) chord-labels)
|
d@33
|
447 (setf best-likelihood nil))))
|
d@33
|
448
|
d@33
|
449 (defun best-level (anchored-period music
|
d@33
|
450 &key (chordset *full-set*)
|
d@33
|
451 (models *default-models*))
|
d@33
|
452 ;; Takes a period for the largest time-unit being considered and
|
d@33
|
453 ;; returns the highest probability subdivision, its likelihood
|
d@33
|
454 ;; values (and the probability of that subdivision, but that's a bit
|
d@33
|
455 ;; of a coincidence and may want not to happen)
|
d@33
|
456 (best-level-hypothesis (make-metrical-divisions anchored-period music)
|
d@33
|
457 music :chordset chordset :models models))
|
d@33
|
458
|
d@33
|
459 (defgeneric make-metrical-divisions (anchored-period music))
|
d@33
|
460 (defmethod make-metrical-divisions ((anchored-period anchored-period)
|
d@33
|
461 (music composition))
|
d@33
|
462 ;; Prepares a set of divisions of the period based on time-sig and a
|
d@33
|
463 ;; pre-set list of options for each possible time-sig numerator.
|
d@33
|
464 (let ((time-sigs (get-applicable-time-signatures anchored-period music)))
|
d@33
|
465 (if
|
d@33
|
466 (< (length time-sigs) 2)
|
d@33
|
467 (let ((candidates))
|
d@33
|
468 ;; get an appropriate set of divisions. Not sure this is right
|
d@33
|
469 ;; - it relies on bar position being irrelevant. Is this true?
|
d@33
|
470 ;; This isn't really clear from this code, but if there are no
|
d@33
|
471 ;; time-signatures, make-divisions-with-timesigs has a test for
|
d@33
|
472 ;; it and will pretend it's 4/4.
|
d@33
|
473 (dolist (divisions (make-divisions-with-time-signature anchored-period (car time-sigs))
|
d@33
|
474 candidates)
|
d@33
|
475 (do ((time (onset anchored-period) (cut-off (car candidate-set)))
|
d@33
|
476 (divisions divisions (cdr divisions))
|
d@33
|
477 (candidate-set))
|
d@33
|
478 ((null divisions) (push (reverse candidate-set) candidates))
|
d@33
|
479 (push (make-anchored-period (timepoint time) (first divisions))
|
d@33
|
480 candidate-set))))
|
d@33
|
481 ;; otherwise, there are lots. Run this function once for each
|
d@33
|
482 ;; time-signature.
|
d@33
|
483 (loop for time-sig in time-sigs
|
d@33
|
484 nconc (make-metrical-divisions (period-intersection anchored-period time-sig)
|
d@33
|
485 music)))))
|
d@33
|
486
|
d@33
|
487 (defgeneric make-divisions-with-time-signature (period time-signature))
|
d@33
|
488 (defmethod make-divisions-with-time-signature ((period period-designator)
|
d@33
|
489 (time-signature basic-time-signature))
|
d@33
|
490 (let* ((numerator (time-signature-numerator time-signature))
|
d@33
|
491 (denominator (time-signature-denominator time-signature))
|
d@33
|
492 (path-options (cdr (assoc numerator *path-options*))))
|
d@33
|
493 (loop for divisions in path-options
|
d@33
|
494 collect (period-fill period divisions denominator))))
|
d@33
|
495
|
d@33
|
496 (defmethod make-divisions-with-time-signature ((period period-designator)
|
d@33
|
497 time-signature)
|
d@33
|
498 ;; not a known time-signature type. Assume 4/4
|
d@33
|
499 (let ((path-options (cdr (assoc 4 *path-options*))))
|
d@33
|
500 (loop for divisions in path-options
|
d@33
|
501 collect (period-fill period divisions 4))))
|
d@33
|
502
|
d@33
|
503 (defun period-fill (period path-options denominator)
|
d@33
|
504 ;; take a division of the ?bar and then repeat it until the period
|
d@33
|
505 ;; is filled.
|
d@33
|
506 ;;
|
d@33
|
507 ;; Perhaps this and surrounding function need to make more use of
|
d@33
|
508 ;; time interface?
|
d@33
|
509 (let ((duration-list)
|
d@33
|
510 ;; Multiply path-options by unit of meter.
|
d@33
|
511 (path-options (map 'list
|
d@33
|
512 #'(lambda (x) (* x (/ 4 denominator)))
|
d@33
|
513 path-options)))
|
d@33
|
514 (do* ((circular-path path-options (or (cdr circular-path)
|
d@33
|
515 path-options))
|
d@33
|
516 (current-duration (car circular-path) (car circular-path))
|
d@33
|
517 (prev-remaining (duration period) remaining)
|
d@33
|
518 (remaining (- (duration period) current-duration) (- remaining current-duration)))
|
d@33
|
519 ((<= remaining 0) (reverse (cons prev-remaining duration-list)))
|
d@33
|
520 (push current-duration duration-list))))
|
d@33
|
521
|
d@33
|
522 (defun best-level-hypothesis (division-hypotheses music
|
d@33
|
523 &key (chordset *full-set*)
|
d@33
|
524 (models *default-models*))
|
d@33
|
525 ;; Rather messy wrapper for level-hypothesis-likelihoods. Should
|
d@33
|
526 ;; probably make this a structure or something, but use looks like
|
d@33
|
527 ;; being quite limited. might revisit.
|
d@33
|
528 (first (sort (level-hypothesis-likelihoods division-hypotheses
|
d@33
|
529 music
|
d@33
|
530 :chordset chordset
|
d@33
|
531 :models models)
|
d@33
|
532 #'> :key #'third)))
|
d@33
|
533
|
d@33
|
534 (defun level-hypothesis-likelihoods (division-hypotheses music
|
d@33
|
535 &key (chordset *full-set*)
|
d@33
|
536 (models *default-models*))
|
d@33
|
537 ;; This function takes the candidate windows being considered (as
|
d@33
|
538 ;; lists of anchored periods) and, for each, works out likelihoods
|
d@33
|
539 ;; and the most probable hypothesis. This should come from taking
|
d@33
|
540 ;; the likelihoods and dividing by the product of the internal sums
|
d@33
|
541 ;; (don't ask!)
|
d@33
|
542 (let ((hypothesis-likelihoods))
|
d@33
|
543 (dolist (hypothesis division-hypotheses hypothesis-likelihoods)
|
d@33
|
544 (let ((likelihoods (map 'list
|
d@33
|
545 #'(lambda (x)
|
d@33
|
546 (get-chord-likelihoods x music models chordset))
|
d@33
|
547 hypothesis)))
|
d@33
|
548 (push (list hypothesis likelihoods (combined-likelihoods-sum likelihoods))
|
d@33
|
549 hypothesis-likelihoods)))))
|
d@33
|
550
|
d@33
|
551 (defun get-chord-likelihoods (anchored-period music models chordset)
|
d@33
|
552 (let ((model-likelihoods
|
d@33
|
553 (loop for model in models
|
d@33
|
554 collect (get-chord-likelihoods-for-model anchored-period
|
d@33
|
555 music
|
d@33
|
556 :model model
|
d@33
|
557 :chordset chordset))))
|
d@33
|
558 (combine-multimodel-likelihoods model-likelihoods)))
|
d@33
|
559
|
d@33
|
560 (defun combine-multimodel-likelihoods (likelihoods-list)
|
d@33
|
561 (cond
|
d@33
|
562 ((= (length likelihoods-list) 1)
|
d@33
|
563 (car likelihoods-list))
|
d@33
|
564 (t
|
d@33
|
565 (let ((combined-likelihoods))
|
d@33
|
566 (dolist (reference-likelihood (car likelihoods-list) combined-likelihoods)
|
d@33
|
567 (setf combined-likelihoods
|
d@33
|
568 (set-likelihood combined-likelihoods
|
d@33
|
569 (likelihood-chordtype reference-likelihood)
|
d@33
|
570 (likelihood-pitch-class reference-likelihood)
|
d@33
|
571 (apply #'* (loop for model-likelihoods in likelihoods-list
|
d@33
|
572 collect (likelihood-likelihood
|
d@33
|
573 (assoc (car reference-likelihood)
|
d@33
|
574 model-likelihoods
|
d@33
|
575 :test #'equal)))))))))))
|
d@33
|
576
|
d@33
|
577 ;;;;;;;;;;;;;;;;;;;;;;;;;
|
d@33
|
578 ;;
|
d@33
|
579 ;; Likelihood (structure) manipulation and access methods
|
d@33
|
580 ;;
|
d@41
|
581 ;; FIXME: Explain this?? Is it ((pc :type) . p(chord))?
|
d@33
|
582
|
d@33
|
583 (defgeneric set-likelihood (likelihoods offset chord likelihood))
|
d@33
|
584 (defmethod set-likelihood ((likelihoods list) chord offset likelihood)
|
d@33
|
585 (acons (list offset chord) likelihood likelihoods))
|
d@33
|
586
|
d@33
|
587 (defgeneric get-likelihood (likelihoods offset chord))
|
d@33
|
588 (defmethod get-likelihood ((likelihoods list) offset chord)
|
d@33
|
589 (assoc (list offset chord) likelihoods :test #'equal))
|
d@33
|
590
|
d@33
|
591 (defgeneric best-n-likelihoods (n likelihoods))
|
d@33
|
592 (defmethod best-n-likelihoods (n (likelihoods list))
|
d@33
|
593 (let ((ranked (ordered-likelihoods likelihoods)))
|
d@33
|
594 (subseq ranked 0 n)))
|
d@33
|
595
|
d@33
|
596 (defgeneric ordered-likelihoods (likelihoods))
|
d@33
|
597 (defmethod ordered-likelihoods ((likelihoods list))
|
d@33
|
598 (sort (copy-seq likelihoods) #'> :key #'cdr))
|
d@33
|
599
|
d@33
|
600 (defgeneric pretty-display-likelihoods (likelihoods))
|
d@33
|
601 (defmethod pretty-display-likelihoods ((likelihoods list))
|
d@33
|
602 (dolist (p likelihoods)
|
d@33
|
603 (format *standard-output* "~%~A~C~A~C~A"
|
d@33
|
604 (likelihood-key p) #\Tab
|
d@33
|
605 (chord-label (likelihood-chordtype p)) #\Tab
|
d@33
|
606 (likelihood-likelihood p))))
|
d@33
|
607
|
d@33
|
608 (defgeneric likelihood-key (likelihood))
|
d@33
|
609 (defmethod likelihood-key ((likelihood list))
|
d@33
|
610 (aref *keys* (first (first likelihood))))
|
d@33
|
611
|
d@33
|
612 (defgeneric likelihood-pitch-class (likelihood))
|
d@33
|
613 (defmethod likelihood-pitch-class ((likelihood list))
|
d@33
|
614 (first (first likelihood)))
|
d@33
|
615
|
d@33
|
616 (defgeneric likelihood-chordtype (likelihood))
|
d@33
|
617 (defmethod likelihood-chordtype ((likelihood list))
|
d@33
|
618 (second (first likelihood)))
|
d@33
|
619
|
d@33
|
620 (defgeneric likelihood-chord (likelihood))
|
d@33
|
621 (defmethod likelihood-chord ((likelihood list))
|
d@33
|
622 (first likelihood))
|
d@33
|
623
|
d@33
|
624 (defgeneric likelihood-likelihood (likelihood))
|
d@33
|
625 (defmethod likelihood-likelihood ((likelihood list))
|
d@33
|
626 (cdr likelihood))
|
d@33
|
627
|
d@33
|
628 (defgeneric likelihoods-sum (likelihoods))
|
d@33
|
629 (defmethod likelihoods-sum ((likelihoods list))
|
d@33
|
630 (loop for likelihood in likelihoods
|
d@33
|
631 sum (likelihood-likelihood likelihood)))
|
d@33
|
632
|
d@33
|
633 (defgeneric combined-likelihoods-sum (combined-likelihoods))
|
d@33
|
634 (defmethod combined-likelihoods-sum ((combined-likelihoods list))
|
d@33
|
635 ;; Will be needed for hypothesis comparison - sums the likelihoods
|
d@33
|
636 ;; for all chords within a window for multiple likelihood
|
d@33
|
637 ;; calculations
|
d@33
|
638 (apply #'* (map 'list #'(lambda (window)
|
d@33
|
639 (loop for likelihood in window
|
d@33
|
640 sum (likelihood-likelihood likelihood)))
|
d@33
|
641 combined-likelihoods)))
|
d@33
|
642
|
d@33
|
643 (defgeneric normalise-likelihoods (likelihoods))
|
d@33
|
644 (defmethod normalise-likelihoods ((likelihoods list))
|
d@33
|
645 (let ((p-sum (sum-likelihoods likelihoods)))
|
d@33
|
646 (if (= p-sum 1)
|
d@33
|
647 likelihoods
|
d@33
|
648 (scale-likelihoods likelihoods (/ 1 p-sum)))))
|
d@33
|
649
|
d@33
|
650 (defgeneric scale-likelihoods (likelihoods scale-factor))
|
d@33
|
651 (defmethod scale-likelihoods ((likelihoods list) (scale-factor number))
|
d@33
|
652 (map 'list #'(lambda (x)
|
d@33
|
653 (cons (first x)
|
d@33
|
654 (* (cdr x) scale-factor)))
|
d@33
|
655 likelihoods))
|
d@33
|
656 
|
d@33
|
657 (defgeneric sum-likelihoods (likelihoods))
|
d@33
|
658 (defmethod sum-likelihoods ((likelihoods list))
|
d@33
|
659 (apply #'+ (map 'list #'cdr likelihoods)))
|
d@33
|
660
|
d@33
|
661 ;; Probably useless vestigial stuff from here
|
d@33
|
662
|
d@33
|
663 (defun vector-list-apply (predicate vector-list &optional other-args)
|
d@33
|
664 (let ((result-list))
|
d@33
|
665 (dolist (vector vector-list (reverse result-list))
|
d@33
|
666 (push (make-array (array-dimensions vector)) result-list)
|
d@33
|
667 (loop for i from 0 to (1- (length vector))
|
d@33
|
668 do (setf (aref (first result-list) i)
|
d@33
|
669 (apply predicate (cons (aref vector i) other-args)))))))
|
d@33
|
670
|
d@33
|
671 (defun vector-sum (vector)
|
d@33
|
672 (loop for i from 0 to (1- (length vector))
|
d@33
|
673 sum (aref vector i)))
|
d@33
|
674
|
d@33
|
675 (defun make-flat-result (chordset)
|
d@33
|
676 (map 'list #'(lambda (x)
|
d@33
|
677 (make-array (length (distribution x))
|
d@33
|
678 :initial-element 0))
|
d@33
|
679 (chords chordset)))
|
d@33
|
680
|
d@33
|
681 (defun key-name (pitch-class)
|
d@33
|
682 (if pitch-class
|
d@33
|
683 (aref #("C" "C#" "D" "Eb" "E" "F" "F#" "G" "G#" "A" "Bb" "B") (mod pitch-class 12))
|
d@33
|
684 nil)) |