d@164
|
1 ;; This file is for methods creating and using gsharp score buffers
|
d@164
|
2 ;; for output. This would generally involve rendering to screen, file,
|
d@164
|
3 ;; printer or browser.
|
d@164
|
4 ;;
|
d@164
|
5 ;; The interface doesn't obviously align perfectly with the gsharp
|
d@164
|
6 ;; data structure, so some notes on the key elements are included
|
d@164
|
7 ;; here:
|
d@164
|
8 ;;
|
d@164
|
9 ;; amuse:events have no direct analogue, with their attributes divided
|
d@164
|
10 ;; between notes and clusters:
|
d@164
|
11 ;;
|
d@164
|
12 ;; * notes have a pitch with a number that is 1- the diatonic
|
d@164
|
13 ;; component of mips pitch and an accidental that is one of a list of
|
d@164
|
14 ;; keyword options, most relevant being :sharp :natural :flat
|
d@164
|
15 ;; :double-sharp and :double-flat
|
d@164
|
16 ;;
|
d@164
|
17 ;; * clusters are the rhythmic and positional aspect of one or more
|
d@164
|
18 ;; notes. Duration is stored in terms of notehead (:long :breve :whole
|
d@164
|
19 ;; :half :filled) and, for :filled, lbeams and rbeams for the number
|
d@164
|
20 ;; of beams in each direction, supplemented by a dots slot. To render
|
d@164
|
21 ;; this to a number for standard time within amuse, there is a method,
|
d@164
|
22 ;; gsharp:duration, for which a crotchet is 1/4
|
d@164
|
23 ;;
|
d@164
|
24 ;; Parts and staffs are viewed notationally, so we have largely
|
d@164
|
25 ;; independant structures called layers (roughly = voices) and staves
|
d@164
|
26 ;; (as they appear on the page). These have names and, at the moment,
|
d@164
|
27 ;; are being identified by a string-getting method below. Within a
|
d@164
|
28 ;; layer, clusters are to be found in bar objects and listed in the
|
d@164
|
29 ;; bars slot.
|
d@164
|
30 ;;
|
d@164
|
31 ;; The musical entity into which these slot is a segment, and there
|
d@164
|
32 ;; can be multiple segments in a buffer, though what this means and
|
d@164
|
33 ;; how they relate is as yet a mystery to me.
|
d@164
|
34 ;;
|
d@164
|
35 ;;; IMPLEMENTATION
|
d@164
|
36 ;;
|
d@164
|
37 ;; We're creating a buffer. Lots of this code is adapted from
|
d@164
|
38 ;; gsharp/mxml/mxml.lisp, but wouldn't now be recognised
|
d@164
|
39 ;;
|
d@164
|
40 ;; * Clef is guessed at - we need amuse:get-applicable-clef (and
|
d@164
|
41 ;; amuse:clef)
|
d@177
|
42 ;;
|
d@177
|
43 ;; * Anacruses are ignored unless they have their own time-signatures!
|
d@177
|
44 ;;
|
d@177
|
45 ;; The stages used are:
|
d@177
|
46 ;; 1 - find layers (1:1 with staves at the moment)
|
d@177
|
47 ;; 2 - for each layer, get events and their timings (includes
|
d@177
|
48 ;; rounding/quantization)
|
d@177
|
49 ;; 3 - Find all ties needed for graphical/courtesy reasons (includes
|
d@177
|
50 ;; removal of extraneous rests and other artefacts)
|
d@177
|
51 ;; 4 - Generate gsharp clusters
|
d@177
|
52 ;; 5 - Get gsharp pitch for events and add notes to clusters
|
d@177
|
53 ;;
|
d@177
|
54
|
d@164
|
55
|
d@164
|
56 (in-package "AMUSE-TOOLS")
|
d@164
|
57
|
d@166
|
58 (defparameter *rounding-factor* 1/4)
|
d@166
|
59
|
d@164
|
60 (defstruct gsharp-duration
|
d@164
|
61 "Structure for specifying duration-related parameters for g-sharp"
|
d@164
|
62 notehead (beams 0) (dots 0) (tied-p nil))
|
d@164
|
63
|
d@164
|
64 ;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
65 ;; Top-level methods
|
d@164
|
66
|
d@177
|
67 (defparameter *foo* nil)
|
d@177
|
68
|
d@164
|
69 (defun write-gsharp-eps (composition pathname)
|
d@164
|
70 ;; write a score eps from a composition. Most of this can be copied
|
d@164
|
71 ;; straight (this is copied already from CSR's code)
|
d@164
|
72 ;; Boilerplate stuff:
|
d@164
|
73 (let* ((frame (clim:make-application-frame 'gsharp:gsharp))
|
d@164
|
74 (clim:*application-frame* frame)
|
d@164
|
75 (esa:*esa-instance* frame))
|
d@164
|
76 (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame)
|
d@164
|
77 (clim:execute-frame-command frame '(gsharp::com-new-buffer))
|
d@164
|
78 ;; Now generate the buffer
|
d@164
|
79 (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition)
|
d@164
|
80 ;; Refresh and process
|
d@164
|
81 (setf (gsharp::modified-p (car (esa:buffers frame))) t)
|
d@164
|
82 (gsharp::recompute-measures (car (esa:buffers frame)))
|
d@164
|
83 ;; Print
|
d@177
|
84 (setf *foo* (car (esa:buffers frame)))
|
d@164
|
85 (clim:execute-frame-command
|
d@164
|
86 frame `(gsharp::com-print-buffer-to-file ,pathname))))
|
d@164
|
87
|
d@164
|
88 ;;;;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
89 ;;
|
d@164
|
90 ;; Big `walking through data structures' type functions
|
d@164
|
91
|
d@164
|
92 (defgeneric fill-gsharp-buffer-with-constituent (buffer constituent)
|
d@164
|
93 (:documentation "Takes an empty gsharp buffer and a constituent and
|
d@164
|
94 fills the buffer based on the contents of constituent. Buffer is
|
d@164
|
95 returned, but this is not necessary, since it is modified in
|
d@164
|
96 place."))
|
d@164
|
97 (defmethod fill-gsharp-buffer-with-constituent (buffer (composition composition))
|
d@164
|
98 ;; FIXME: Throughout this, I assume that
|
d@164
|
99 ;; get-applicable-time-signatures isn't '()
|
d@164
|
100 (let ((time-signatures (get-applicable-time-signatures composition composition))
|
d@183
|
101 (key-signatures (handler-bind ((insufficient-information
|
d@183
|
102 #'(lambda (c)
|
d@183
|
103 (declare (ignore c))
|
d@183
|
104 (invoke-restart 'guess))))
|
d@183
|
105 (get-applicable-key-signatures composition composition)))
|
d@164
|
106 (layers))
|
d@164
|
107 (multiple-value-bind (layer-events layer-scores)
|
d@164
|
108 ;; Get hash-tables of events by layer and counts of events
|
d@164
|
109 ;; below middle C for guessing clef.
|
d@164
|
110 (identify-gsharp-layers composition)
|
d@164
|
111 ;; For each layer make clef, and one staff per layer
|
d@164
|
112 ;; FIXME: this is cheating
|
d@164
|
113 (maphash #'(lambda (name events)
|
d@164
|
114 (let* ((clef (if (> (gethash name layer-scores)
|
d@164
|
115 (/ (length events) 2))
|
d@164
|
116 (gsharp::make-clef :bass)
|
d@164
|
117 (gsharp::make-clef :treble)))
|
d@164
|
118 (staff (gsharp::make-fiveline-staff :name name
|
d@164
|
119 :clef clef)))
|
d@164
|
120 ;; Make the layers
|
d@164
|
121 (push (gsharp::make-layer (list staff)
|
d@164
|
122 :body (gsharp::make-slice :bars nil)
|
d@164
|
123 :name name)
|
d@164
|
124 layers)
|
d@164
|
125 ;; Add the notes and bars
|
d@166
|
126 (add-bars-and-events-to-layer (car layers) (reverse events)
|
d@168
|
127 time-signatures composition
|
d@166
|
128 :key-signatures key-signatures)))
|
d@164
|
129 layer-events)
|
d@164
|
130 ;; Attach layers to a segment and place segment into buffer
|
d@164
|
131 (let* ((segment (make-instance 'gsharp::segment :layers layers :buffer buffer)))
|
d@164
|
132 (setf (gsharp::segments buffer) (list segment)
|
d@164
|
133 (gsharp::staves buffer) (mapcar #'(lambda (x) (car (gsharp::staves x)))
|
d@164
|
134 layers))
|
d@164
|
135 buffer))))
|
d@164
|
136
|
d@164
|
137 (defgeneric identify-gsharp-layers (constituent)
|
d@164
|
138 (:documentation "Takes a composition, returns two hash tables, one
|
d@164
|
139 of events grouped by layer name and the other of counts of notes
|
d@164
|
140 below middle C by layer (for choosing clef). N.B. On two counts this
|
d@164
|
141 is a dodgy way of tying clef, staff and layer. Separate later."))
|
d@164
|
142
|
d@164
|
143 (defmethod identify-gsharp-layers ((composition composition))
|
d@164
|
144 (let ((layer-events (make-hash-table :test #'equal))
|
d@177
|
145 (layer-scores (make-hash-table :test #'equal))
|
d@177
|
146 (layer-clefs (make-hash-table :test #'equal))
|
d@177
|
147 (layer-name))
|
d@164
|
148 (sequence:dosequence (event composition (values layer-events layer-scores))
|
d@164
|
149 (when (pitchedp event)
|
d@177
|
150 (setf layer-name (gsharp-layer-string event))
|
d@164
|
151 (cond
|
d@177
|
152 ((gethash layer-name layer-events)
|
d@177
|
153 (push event (gethash layer-name layer-events))
|
d@164
|
154 (when (< (midi-pitch-number event) 60)
|
d@177
|
155 (incf (gethash layer-name layer-scores))))
|
d@177
|
156 (t (setf (gethash layer-name layer-events)
|
d@164
|
157 (list event)
|
d@177
|
158 (gethash layer-name layer-scores)
|
d@164
|
159 (if (< (midi-pitch-number event) 60)
|
d@164
|
160 1 0))))))))
|
d@164
|
161
|
d@168
|
162 (defun add-bars-and-events-to-layer (layer events time-signatures composition
|
d@168
|
163 &key (key-signatures nil))
|
d@164
|
164 "Given list of events to be attached to a layer, along with
|
d@164
|
165 applicable time signatures, clumsily waddle through them all and
|
d@164
|
166 slap an approximation to the events into place. Improve this."
|
d@177
|
167 (let* (
|
d@177
|
168 ;; (crotchet-beats (duration (crotchet (car events)))) ;; (or make into semibreves?)
|
d@177
|
169 ;; (beat-starts (beat-starts time-signatures
|
d@177
|
170 ;; :crotchet crotchet-beats))
|
d@177
|
171 ;; (bar-starts (mapcar #'car beat-starts))
|
d@177
|
172 (bar-starts (let ((starts))
|
d@183
|
173 (do ((bar-period (current-bar (make-standard-moment 0) composition)
|
d@183
|
174 (current-bar (cut-off bar-period) composition)))
|
d@177
|
175 ((time>= (cut-off bar-period) (cut-off composition))
|
d@177
|
176 (reverse (cons (timepoint bar-period) starts)))
|
d@177
|
177 (push (timepoint bar-period) starts))))
|
d@177
|
178 (beat-starts (if time-signatures
|
d@183
|
179 (handler-bind
|
d@183
|
180 ((insufficient-information
|
d@183
|
181 #'(lambda (c)
|
d@183
|
182 (declare (ignore c))
|
d@183
|
183 (invoke-restart 'use-whole-bar))))
|
d@183
|
184 (let ((starts) (current))
|
d@183
|
185 (do* ((bars bar-starts)
|
d@183
|
186 (beat-period (current-beat (make-standard-moment 0) composition)
|
d@183
|
187 (current-beat (cut-off beat-period) composition))
|
d@183
|
188 (beat-time (timepoint beat-period) (timepoint beat-period)))
|
d@183
|
189 ((time>= (cut-off beat-period) (cut-off composition))
|
d@183
|
190 (progn
|
d@183
|
191 (when (and (cdr bars)
|
d@183
|
192 (>= beat-time (second bars)))
|
d@183
|
193 (push (reverse current) starts)
|
d@183
|
194 (setf current nil
|
d@183
|
195 bars (cdr bars)))
|
d@183
|
196 (push beat-time current)
|
d@183
|
197 (reverse (cons (reverse current) starts))))
|
d@183
|
198 (when (and (cdr bars)
|
d@183
|
199 (>= beat-time (second bars)))
|
d@183
|
200 (push (reverse current) starts)
|
d@183
|
201 (setf current nil
|
d@183
|
202 bars (cdr bars)))
|
d@183
|
203 (push beat-time current))))
|
d@183
|
204
|
d@183
|
205 (mapcar #'list bar-starts)))
|
d@177
|
206 (ons) (position)
|
d@166
|
207 (clusters) (bar) (bar-no 0)
|
d@164
|
208 (body (gsharp::body layer)))
|
d@166
|
209 ;; this is a cheat to guess timing rounding (quantisation) based
|
d@166
|
210 ;; on onset times - only affects midi-like data where onsets are
|
d@166
|
211 ;; already rounded, but durations are not (as in TC's fantasia
|
d@166
|
212 ;; midi files....)
|
d@166
|
213 (setf *rounding-factor* (max (guess-rounding-factor events)
|
d@166
|
214 1/8))
|
d@166
|
215 ;; First create a list of change-points for when events are
|
d@166
|
216 ;; sounding, of the format (time event event event) (time event))
|
d@166
|
217 (dolist (event events)
|
d@166
|
218 (setf ons (add-on-off-pair event ons)))
|
d@166
|
219 ;; These durations may span bars, which is an absolute ban for
|
d@166
|
220 ;; most music (Mensurstrich aside), so insert bar starts if not
|
d@166
|
221 ;; present. Note that, since the events themselves are recorded in
|
d@166
|
222 ;; the list, the existence of ties shuold be detected.
|
d@166
|
223 (when bar-starts
|
d@166
|
224 (setf ons (add-bar-starts-if-not-present bar-starts ons)))
|
d@166
|
225 ;; Finally, one problem here is that, in midi, there is often a
|
d@166
|
226 ;; gap or overlap between consecutive notes or chords. Since
|
d@166
|
227 ;; rounding happens, but there is no check for bar length here or
|
d@166
|
228 ;; within g-sharp, this should verify that everything makes
|
d@166
|
229 ;; sense. At the moment, it just removes short rests...
|
d@177
|
230 (setf ons (check-ons ons bar-starts))
|
d@166
|
231 ;; Now create the bars and the gsharp clusters
|
d@168
|
232 (when key-signatures
|
d@168
|
233 (setf (gsharp::keysig (car (gsharp::staves layer)))
|
d@168
|
234 (make-gsharp-key-signature (car key-signatures) layer)))
|
d@166
|
235 (do ((old-ons nil ons)
|
d@166
|
236 (ons ons (cdr ons)))
|
d@166
|
237 ((null (cdr ons)))
|
d@166
|
238 (when (member (caar ons) bar-starts)
|
d@177
|
239 ;; We're at the beginning of a bar.
|
d@177
|
240 (when bar (check-beams bar))
|
d@166
|
241 (setf bar (gsharp::make-melody-bar))
|
d@166
|
242 (gsharp::add-bar bar body bar-no)
|
d@177
|
243 (incf bar-no)
|
d@177
|
244 (setf position 0))
|
d@183
|
245 #+nil (when (and key-signatures
|
d@168
|
246 (<= (timepoint (car key-signatures))
|
d@168
|
247 (caar ons)))
|
d@177
|
248 (gsharp::add-element (make-gsharp-key-signature (car key-signatures) layer)
|
d@177
|
249 bar
|
d@177
|
250 position)
|
d@177
|
251 (incf position)
|
d@168
|
252 (setf key-signatures (cdr key-signatures)))
|
d@166
|
253 ;; A quick check for notes which span beats and don't start at
|
d@166
|
254 ;; the beginning of their beats. IMO, this makes them more
|
d@166
|
255 ;; likely to require a tie.
|
d@177
|
256 (when (and (cdr ons)
|
d@177
|
257 (not (member (caar ons)
|
d@166
|
258 (car beat-starts)))
|
d@166
|
259 (find-if #'(lambda (x) (> x (caar ons)))
|
d@177
|
260 (car beat-starts))
|
d@166
|
261 (< (find-if #'(lambda (x) (> x (caar ons)))
|
d@177
|
262 (car beat-starts))
|
d@166
|
263 (car (second ons))))
|
d@166
|
264 (setf (cdr ons)
|
d@166
|
265 (cons (cons (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
266 (car beat-starts))
|
d@166
|
267 (cdar ons))
|
d@166
|
268 (cdr ons))))
|
d@166
|
269 ;; Making clusters just from duration removes the ability to
|
d@166
|
270 ;; divide notes into easy-to-read tied components based on the
|
d@166
|
271 ;; time signature (for example, a note of a tactus beat + a
|
d@166
|
272 ;; quaver in 6/8 will be rendered as a minim this way) - that's
|
d@166
|
273 ;; why I've taken as much of the metrical logic out and put it
|
d@166
|
274 ;; above if there are other straightforward rules, they should,
|
d@166
|
275 ;; I think go there.
|
d@177
|
276 (if (cdar ons)
|
d@166
|
277 (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons))
|
d@177
|
278 (car (car ons)))))
|
d@166
|
279 (setf clusters (make-gsharp-rests-with-duration (- (car (second ons))
|
d@177
|
280 (car (car ons)))
|
d@177
|
281 layer)))
|
d@177
|
282 (let ((now (caar ons)) (first-p t) (pitches))
|
d@166
|
283 (do ((clusters clusters (cdr clusters)))
|
d@166
|
284 ((null clusters))
|
d@166
|
285 (when (member now (car beat-starts))
|
d@166
|
286 (setf (gsharp::lbeams (car clusters)) 0))
|
d@164
|
287 ;; This function adds cluster at a specific point in the
|
d@164
|
288 ;; bar. It does a lot of other things that are probably a)
|
d@164
|
289 ;; not necessary or b) should be within the duration logic
|
d@164
|
290 ;; above. Would be good not to rely on it (which is not to
|
d@164
|
291 ;; say that it isn't reliable)
|
d@177
|
292 (gsharp::add-element (car clusters) bar position)
|
d@177
|
293 ;; FIXME: Deleting notes that fall on the same note
|
d@177
|
294 ;; name. Stupid thing to do.
|
d@177
|
295 (setf pitches (remove-duplicates (mapcar #'(lambda (x)
|
d@177
|
296 (cons x (pitch-for-gsharp x composition)))
|
d@177
|
297 (cdar ons))
|
d@177
|
298 :key #'second :test #'=))
|
d@177
|
299 (dolist (pitch pitches)
|
d@168
|
300 (with-simple-restart (ignore "Ignore note")
|
d@177
|
301 (gsharp::add-note (car clusters)
|
d@177
|
302 (make-instance 'gsharp::note
|
d@177
|
303 :pitch (second pitch)
|
d@177
|
304 :accidentals (third pitch)
|
d@177
|
305 :staff (car (gsharp::staves layer))
|
d@177
|
306 :tie-right (if (or (cdr clusters)
|
d@177
|
307 (member (car pitch) (second ons)))
|
d@177
|
308 t
|
d@177
|
309 nil)
|
d@177
|
310 :tie-left (if first-p
|
d@177
|
311 (member (car pitch) (first old-ons))
|
d@177
|
312 t)))))
|
d@166
|
313 (incf now (* (gsharp::duration (car clusters)) 4))
|
d@177
|
314 (setf first-p nil)
|
d@177
|
315 (incf position)))
|
d@166
|
316 (when (and (cdr bar-starts)
|
d@166
|
317 (= (car (second ons))
|
d@166
|
318 (second bar-starts)))
|
d@166
|
319 (setf bar-starts (cdr bar-starts)
|
d@168
|
320 beat-starts (cdr beat-starts))))))
|
d@168
|
321
|
d@177
|
322 (defun check-beams (bar)
|
d@177
|
323 (do* ((clusters (gsharp::elements bar) (cdr clusters))
|
d@177
|
324 (left) (mid) (right))
|
d@177
|
325 ((null (cddr clusters)))
|
d@177
|
326 (setf left (first clusters)
|
d@177
|
327 mid (second clusters)
|
d@177
|
328 right (third clusters))
|
d@177
|
329 (unless (or (typep mid 'gsharp::rest)
|
d@177
|
330 (= (max (gsharp::rbeams mid)
|
d@177
|
331 (gsharp::lbeams mid))
|
d@177
|
332 0))
|
d@177
|
333 (cond
|
d@177
|
334 ((or (typep left 'gsharp::rest)
|
d@177
|
335 (= (gsharp::rbeams left) 0))
|
d@177
|
336 (setf (gsharp::lbeams mid) 0))
|
d@177
|
337 ((or (typep right 'gsharp::rest)
|
d@177
|
338 (= (gsharp::lbeams right) 0))
|
d@177
|
339 (setf (gsharp::rbeams mid) 0))
|
d@177
|
340 ((< (gsharp::rbeams left)
|
d@177
|
341 (gsharp::lbeams right))
|
d@177
|
342 (setf (gsharp::lbeams mid) (gsharp::rbeams left)))
|
d@177
|
343 (t (setf (gsharp::rbeams mid) (gsharp::lbeams right)))))))
|
d@168
|
344
|
d@168
|
345 (defgeneric make-gsharp-key-signature (key-signature layer))
|
d@168
|
346 (defmethod make-gsharp-key-signature ((key-signature standard-key-signature) layer)
|
d@183
|
347 (let ((alterations (make-array 7 :initial-element :natural))
|
d@168
|
348 (order-of-sharps #(3 0 4 1 5 2 6))
|
d@168
|
349 (order-of-flats #(6 2 5 1 4 0 3)))
|
d@168
|
350 (if (< (key-signature-sharps key-signature) 0)
|
d@168
|
351 (dotimes (index (abs (key-signature-sharps key-signature)))
|
d@168
|
352 (setf (elt alterations (elt order-of-flats index)) :flat))
|
d@168
|
353 (dotimes (index (key-signature-sharps key-signature))
|
d@168
|
354 (setf (elt alterations (elt order-of-sharps index)) :sharp)))
|
d@168
|
355 (gsharp-buffer::make-key-signature (car (gsharp::staves layer))
|
d@168
|
356 :alterations alterations)))
|
d@164
|
357
|
d@164
|
358 ;;;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
359 ;;
|
d@164
|
360 ;; Information conversion functions
|
d@164
|
361
|
d@164
|
362 ;; Pitch
|
d@164
|
363
|
d@168
|
364 (defgeneric pitch-for-gsharp (pitch composition)
|
d@164
|
365 (:documentation "Given a pitch object, return a list of gsharp's
|
d@164
|
366 pitch number and accidental keyword"))
|
d@168
|
367 (defmethod pitch-for-gsharp ((pitch diatonic-pitch) composition)
|
d@168
|
368 (declare (ignore composition))
|
d@164
|
369 ;; Easy for diatonic pitch, although behaviour for extreme or
|
d@164
|
370 ;; fractional alterations is unclear.
|
d@164
|
371 (list (1+ (diatonic-pitch-mp pitch))
|
d@164
|
372 (case (diatonic-pitch-accidental pitch)
|
d@164
|
373 (1 :sharp)
|
d@164
|
374 (-1 :flat)
|
d@164
|
375 (2 :double-sharp)
|
d@164
|
376 (-2 :double-flat)
|
d@164
|
377 (0 :natural)
|
d@164
|
378 (otherwise (error "gsharp can't handle this pitch")))))
|
d@168
|
379 (defmethod pitch-for-gsharp ((pitch chromatic-pitch) composition)
|
d@168
|
380 ;; Just go for line-of-fifths proximity spelling, but based on
|
d@168
|
381 ;; keysig if present. Could always try to spell it with ps13, but..
|
d@164
|
382 (let* ((octave (octave pitch))
|
d@164
|
383 (pitch-class (pitch-class pitch))
|
d@164
|
384 (diatonic-pitch-number (aref #(0 0 1 2 2 3 3 4 4 5 6 6) pitch-class)))
|
d@164
|
385 (list (+ (* 7 octave) diatonic-pitch-number)
|
d@164
|
386 (aref #(:natural :sharp ;; C C#
|
d@164
|
387 :natural ;; D
|
d@164
|
388 :flat :natural ;; Eb E
|
d@164
|
389 :natural :sharp ;; F F#
|
d@164
|
390 :natural :sharp ;; G G#
|
d@164
|
391 :natural ;; A
|
d@164
|
392 :flat :natural) ;; Bb B
|
d@164
|
393 pitch-class))))
|
d@168
|
394 (defmethod pitch-for-gsharp ((event standard-chromatic-pitched-event) composition)
|
d@168
|
395 ;; Should probably go for line-of-fifths proximity spelling,
|
d@168
|
396 ;; if keysig present, but ps13ing for now.
|
d@168
|
397 (let* ((octave (octave event))
|
d@177
|
398 (event-pos (position event (ocp-list composition) :key #'car))
|
d@168
|
399 (note-sequence (get-spelling-list composition))
|
d@168
|
400 (spelling (elt note-sequence event-pos))
|
d@168
|
401 (note-name (cdr (assoc (aref (second spelling) 0)
|
d@168
|
402 '(("C" . 0) ("D" . 1) ("E" . 2) ("F" . 3)
|
d@168
|
403 ("G" . 4) ("A" . 5) ("B" . 6))
|
d@168
|
404 :test #'string=)))
|
d@168
|
405 (accidental (cdr (assoc (aref (second spelling) 1)
|
d@168
|
406 '(("ss" . :double-sharp) ("s" . :sharp) ("n" . :natural)
|
d@168
|
407 ("f" . :flat) ("ff" . :double-flat))
|
d@168
|
408 :test #'string=))))
|
d@168
|
409 (list (+ (* 7 octave) note-name) accidental)))
|
d@168
|
410
|
d@168
|
411
|
d@168
|
412 (defparameter *spelling-cache* (make-hash-table))
|
d@168
|
413 (defparameter *ocp-cache* (make-hash-table))
|
d@168
|
414
|
d@168
|
415 (defun get-spelling-list (composition)
|
d@168
|
416 (unless (gethash composition *spelling-cache*)
|
d@168
|
417 (setf (gethash composition *spelling-cache*)
|
d@168
|
418 (ps13:ps13-new-imp (map 'list #'cdr (get-ocp-list composition))
|
d@168
|
419 10 42 nil nil nil)))
|
d@168
|
420 (gethash composition *spelling-cache*))
|
d@168
|
421
|
d@168
|
422 (defun get-ocp-list (composition)
|
d@168
|
423 (unless (gethash composition *ocp-cache*)
|
d@168
|
424 (setf (gethash composition *ocp-cache*) (ocp-list composition)))
|
d@168
|
425 (gethash composition *ocp-cache*))
|
d@168
|
426
|
d@168
|
427 (defun ocp-list (composition)
|
d@168
|
428 (flet ((sorter (x y)
|
d@168
|
429 (or (amuse:time< x y)
|
d@168
|
430 (and (amuse:time= x y)
|
d@168
|
431 (amuse:pitch< x y)))))
|
d@168
|
432 (loop for e being each element of composition
|
d@168
|
433 if (typep e 'amuse:pitched-event)
|
d@168
|
434 collect (cons e (make-array 2 :initial-contents
|
d@168
|
435 (list (slot-value e 'amuse::time)
|
d@168
|
436 (- (slot-value e 'amuse::number) 21)))) into result
|
d@168
|
437 finally (return (sort result #'sorter :key #'car)))))
|
d@168
|
438
|
d@168
|
439
|
d@168
|
440
|
d@164
|
441
|
d@164
|
442 ;; Time
|
d@164
|
443
|
d@166
|
444 (defun make-gsharp-clusters-with-duration (duration)
|
d@166
|
445 "Returns a list of cluster(s) whose total duration is equal to
|
d@166
|
446 duration (which is given in crotchets)"
|
d@166
|
447 (let ((new-durations (gsharp-durations-from-beats duration)))
|
d@166
|
448 (loop for new-duration in new-durations
|
d@166
|
449 collect (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration)
|
d@166
|
450 :lbeams (gsharp-duration-beams new-duration)
|
d@166
|
451 :rbeams (gsharp-duration-beams new-duration)
|
d@166
|
452 :dots (gsharp-duration-dots new-duration)))))
|
d@166
|
453
|
d@166
|
454 (defun make-gsharp-rests-with-duration (duration layer)
|
d@166
|
455 "Returns a list of rest(s) whose total duration is equal to
|
d@166
|
456 duration (which is given in crotchets)"
|
d@166
|
457 (let ((new-durations (gsharp-durations-from-beats duration)))
|
d@166
|
458 (loop for new-duration in new-durations
|
d@166
|
459 collect(gsharp::make-rest (car (gsharp::staves layer))
|
d@166
|
460 :notehead (gsharp-duration-notehead new-duration)
|
d@166
|
461 :lbeams (gsharp-duration-beams new-duration)
|
d@166
|
462 :rbeams (gsharp-duration-beams new-duration)
|
d@166
|
463 :dots (gsharp-duration-dots new-duration)))))
|
d@164
|
464
|
d@164
|
465 (defun gsharp-durations-from-beats (beats &optional (durations nil))
|
d@164
|
466 ;; Takes a count of crotchets and returns a list of
|
d@164
|
467 ;; <gsharp-duration>s that most simply defines the attached. This
|
d@164
|
468 ;; is a recursive function that finds the longest simple duration
|
d@164
|
469 ;; that fits into beats and, if that leaves a remainder, runs again
|
d@164
|
470 ;; on the remainder (until hemi-demi-semi-quavers are reached). It
|
d@164
|
471 ;; avoids double dots and is ignorant of time-signature. It will be
|
d@164
|
472 ;; replaced. Soon.
|
d@164
|
473 ;;; FIXME: Handles quantisation fairly
|
d@164
|
474 ;; stupidly. Could be made slightly smarter with simple rounding
|
d@164
|
475 ;; (erring on the side of longer durations?)
|
d@164
|
476 (assert (>= beats 0))
|
d@164
|
477 (push (make-gsharp-duration) durations)
|
d@164
|
478 ;; First find the longest simple duration that fits in beats
|
d@164
|
479 ;; First with notes > 1 crotchet
|
d@164
|
480 (loop for option in '((16 :long) (8 :breve) (4 :whole) (2 :half) (1 :filled))
|
d@164
|
481 do (cond
|
d@166
|
482 ((= beats (* (car option) 7/4))
|
d@166
|
483 (setf (gsharp-duration-notehead (car durations))
|
d@166
|
484 (cadr option)
|
d@166
|
485 (gsharp-duration-dots (car durations))
|
d@166
|
486 2)
|
d@166
|
487 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
488 ((= beats (* (car option) 3/2))
|
d@164
|
489 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
490 (cadr option)
|
d@164
|
491 (gsharp-duration-dots (car durations))
|
d@164
|
492 1)
|
d@164
|
493 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
494 ((> beats (car option))
|
d@164
|
495 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
496 (cadr option))
|
d@164
|
497 (return-from gsharp-durations-from-beats
|
d@164
|
498 (gsharp-durations-from-beats (- beats (car option)) durations)))
|
d@164
|
499 ((= beats (car option))
|
d@164
|
500 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
501 (cadr option))
|
d@164
|
502 (return-from gsharp-durations-from-beats (reverse durations)))))
|
d@164
|
503 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
504 :filled)
|
d@164
|
505 ;; then with short notes (beams rather than noteheads)
|
d@164
|
506 (do ((i 1 (1+ i)))
|
d@164
|
507 ((= i 4) ;; means either tuplet, very short note or unquantised data
|
d@164
|
508 (reverse durations))
|
d@164
|
509 (cond
|
d@164
|
510 ((= beats (* (/ 1 (expt 2 i)) 3/2))
|
d@164
|
511 (setf (gsharp-duration-beams (car durations))
|
d@164
|
512 i
|
d@164
|
513 (gsharp-duration-dots (car durations))
|
d@164
|
514 1)
|
d@164
|
515 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
516 ((> beats (/ 1 (expt 2 i)))
|
d@164
|
517 (setf (gsharp-duration-beams (car durations))
|
d@164
|
518 i)
|
d@164
|
519 (return-from gsharp-durations-from-beats
|
d@164
|
520 (gsharp-durations-from-beats (- beats (/ 1 (expt 2 i))) durations)))
|
d@164
|
521 ((= beats (/ 1 (expt 2 i)))
|
d@164
|
522 (setf (gsharp-duration-beams (car durations))
|
d@164
|
523 i)
|
d@164
|
524 (return-from gsharp-durations-from-beats (reverse durations))))))
|
d@164
|
525
|
d@164
|
526 ;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
527 ;;
|
d@164
|
528 ;; Other utility functions
|
d@164
|
529
|
d@164
|
530 (defgeneric gsharp-layer-string (event)
|
d@164
|
531 (:method (e) (name-from-channel-and-patch e))
|
d@168
|
532 (:method ((e amuse-gsharp::gsharp-object))
|
d@168
|
533 (name-from-layer e))
|
d@164
|
534 (:documentation "Return a string that uniquely identifies the layer
|
d@164
|
535 to which event belongs"))
|
d@164
|
536
|
d@168
|
537 (defun name-from-layer (event)
|
d@177
|
538 ;; Uses gsharp layer names. Numbers layers in cases of duplication
|
d@168
|
539 (let* ((layers (gsharp::layers
|
d@168
|
540 (car
|
d@168
|
541 (gsharp::segments
|
d@168
|
542 (gsharp::buffer
|
d@168
|
543 (gsharp::staff
|
d@168
|
544 (amuse-gsharp::note event)))))))
|
d@168
|
545 (layer (gsharp::layer
|
d@168
|
546 (gsharp::slice
|
d@168
|
547 (gsharp::bar
|
d@168
|
548 (gsharp::cluster
|
d@168
|
549 (amuse-gsharp::note event))))))
|
d@168
|
550 (name (gsharp::name layer))
|
d@168
|
551 (count))
|
d@168
|
552 (dolist (cand-layer layers)
|
d@168
|
553 (if (eq cand-layer layer)
|
d@168
|
554 (if count
|
d@168
|
555 (return-from name-from-layer (concatenate 'string
|
d@168
|
556 name
|
d@168
|
557 (princ-to-string count)))
|
d@168
|
558 (return-from name-from-layer name))
|
d@168
|
559 (when (string= name (gsharp::name cand-layer))
|
d@168
|
560 (setf count (if count (+ count 1) 2)))))))
|
d@168
|
561
|
d@164
|
562 (defun name-from-channel-and-patch (event)
|
d@164
|
563 "Generate layer-identifying string from the patch and channel that
|
d@164
|
564 would be used for midi export. For MIDI, this is guaranteed to
|
d@164
|
565 separate or over-separate. Tracks would possibly be better, but ?don't
|
d@164
|
566 exist in MIDI type 0?"
|
d@164
|
567 (format nil "~D/~D"
|
d@164
|
568 (get-patch-for-midi event)
|
d@164
|
569 (get-channel-for-midi event)))
|
d@164
|
570
|
d@164
|
571 (defun bar-starts (time-signature-list &key (crotchet 1))
|
d@164
|
572 (loop for time-signature in time-signature-list
|
d@164
|
573 nconc (loop for i from (timepoint (onset time-signature))
|
d@164
|
574 to (1- (timepoint (cut-off time-signature)))
|
d@164
|
575 by (* (crotchets-in-a-bar time-signature) crotchet)
|
d@164
|
576 collect i)))
|
d@166
|
577
|
d@166
|
578 (defun beat-starts (time-signature-list &key (crotchet 1))
|
d@177
|
579 ;; provides a list of bars, each a list of beats. If no timesig,
|
d@177
|
580 ;; guesses at 4/4
|
d@177
|
581 ;; FIXME: This is stupid and should disappear if and when proper
|
d@177
|
582 ;; beat methods are implemented.
|
d@168
|
583 (if time-signature-list
|
d@168
|
584 (loop for time-signature in time-signature-list
|
d@168
|
585 nconc (loop for i from (timepoint (onset time-signature))
|
d@168
|
586 to (1- (timepoint (cut-off time-signature)))
|
d@168
|
587 by (* (crotchets-in-a-bar time-signature) crotchet)
|
d@168
|
588 collect (loop for j from 0
|
d@166
|
589 to (* (crotchets-in-a-bar time-signature)
|
d@166
|
590 crotchet)
|
d@166
|
591 by (* (tactus-duration time-signature)
|
d@166
|
592 crotchet)
|
d@168
|
593 collect (+ j i))))
|
d@168
|
594 ;; FIXME: fudge
|
d@168
|
595 (loop for i from 0 to 1000 by 4
|
d@168
|
596 collect (loop for j from i to (+ i 3)
|
d@168
|
597 collect j))))
|
d@166
|
598
|
d@166
|
599 ;;;;;;;;;;;;;;;;;;
|
d@166
|
600 ;;
|
d@166
|
601 ;; Sequence and data structure functions
|
d@166
|
602
|
d@166
|
603
|
d@166
|
604 (defun add-bar-starts-if-not-present (bar-starts changes)
|
d@166
|
605 "Takes a list of bar-start times and one of sounding notes at
|
d@166
|
606 times. If a bar has no change of sounding notes at its start, it would
|
d@166
|
607 not appear in the latter list, but since we will want notes tied over
|
d@166
|
608 barlines, we must add it and return the modified list"
|
d@166
|
609 (let ((new-changes))
|
d@166
|
610 (dolist (event changes (reverse new-changes))
|
d@166
|
611 (do ()
|
d@166
|
612 ((not (and bar-starts
|
d@166
|
613 (< (car bar-starts)
|
d@166
|
614 (car event)))))
|
d@166
|
615 (setf new-changes
|
d@166
|
616 (cons (if (cadr new-changes)
|
d@166
|
617 (cons (car bar-starts)
|
d@166
|
618 (cdar new-changes))
|
d@166
|
619 (list (car bar-starts)))
|
d@166
|
620 new-changes)
|
d@166
|
621 bar-starts (cdr bar-starts)))
|
d@166
|
622 (setf new-changes (cons event new-changes))
|
d@166
|
623 (when (and bar-starts (= (car event) (car bar-starts)))
|
d@166
|
624 (setf bar-starts (cdr bar-starts))))))
|
d@166
|
625
|
d@166
|
626 (defun add-on-off-pair (event data)
|
d@166
|
627 "For walking through an ordered event sequence and building up a
|
d@166
|
628 list of changes to sounding pitches, this function takes an event and
|
d@166
|
629 adds the time for which it sounds to the structure."
|
d@166
|
630 (let ((copied-data)
|
d@166
|
631 (on (* (round
|
d@166
|
632 (* (timepoint event)
|
d@166
|
633 (duration (crotchet event)))
|
d@166
|
634 *rounding-factor*) *rounding-factor*))
|
d@166
|
635 (off (* (round
|
d@166
|
636 (* (timepoint (cut-off event))
|
d@166
|
637 (duration (crotchet event)))
|
d@166
|
638 *rounding-factor*) *rounding-factor*)))
|
d@166
|
639 (do ((data data (cdr data)))
|
d@166
|
640 ((null data) (reverse (cons (list off)
|
d@166
|
641 (cons (list on event)
|
d@166
|
642 copied-data))))
|
d@166
|
643 (cond
|
d@166
|
644 ((<= on (caar data))
|
d@166
|
645 (when (< on (caar data))
|
d@166
|
646 (push (cons on (cons event (cdr (car copied-data))))
|
d@166
|
647 copied-data))
|
d@166
|
648 (do ((data data (cdr data)))
|
d@166
|
649 ((null data)
|
d@166
|
650 (return-from add-on-off-pair
|
d@166
|
651 (reverse (cons (cons off (cddr (car copied-data)))
|
d@166
|
652 copied-data))))
|
d@166
|
653 (cond
|
d@166
|
654 ((= (caar data) off)
|
d@166
|
655 (return-from add-on-off-pair
|
d@166
|
656 (nconc (reverse copied-data) data)))
|
d@166
|
657 ((> (caar data) off)
|
d@166
|
658 (push (cons off (cddr (car copied-data)))
|
d@166
|
659 copied-data)
|
d@166
|
660 (return-from add-on-off-pair
|
d@166
|
661 (nconc (reverse copied-data) data)))
|
d@166
|
662 ((< (caar data) off)
|
d@166
|
663 (push (cons (caar data)
|
d@166
|
664 (cons event (cdr (car data))))
|
d@166
|
665 copied-data)))))
|
d@166
|
666 (t
|
d@166
|
667 (push (car data) copied-data))))))
|
d@166
|
668
|
d@177
|
669 (defun check-ons (ons bar-starts)
|
d@177
|
670 "looks for small rests such as might be created by midi performance
|
d@177
|
671 of tenuto lines"
|
d@177
|
672 (let ((time (caar ons)) (notes (cdar ons))(new-ons))
|
d@177
|
673 (do ((ons (cdr ons) (cdr ons)))
|
d@177
|
674 ((null ons) (reverse new-ons))
|
d@177
|
675 (if (or (member (caar ons) bar-starts)
|
d@177
|
676 (> (- (caar ons) time)
|
d@177
|
677 *rounding-factor*))
|
d@177
|
678 (setf new-ons (cons (cons time notes) new-ons)
|
d@177
|
679 time (caar ons)
|
d@177
|
680 notes (cdar ons))
|
d@177
|
681 (dolist (note (cdar ons))
|
d@177
|
682 (unless (member note notes)
|
d@177
|
683 (push note notes)))))))
|
d@177
|
684
|
d@177
|
685 #+nil
|
d@177
|
686
|
d@166
|
687 (defun check-ons (ons)
|
d@166
|
688 "looks for small rests such as might be created by midi performance
|
d@166
|
689 of tenuto lines"
|
d@177
|
690 (let ((new-ons) (skip))
|
d@166
|
691 (do ((ons ons (cdr ons)))
|
d@166
|
692 ((null (cdr ons))
|
d@177
|
693 (if skip (reverse new-ons) (reverse (cons (car ons) new-ons))))
|
d@177
|
694 (cond
|
d@177
|
695 (skip (setf skip nil))
|
d@177
|
696 ((not (and (<= (- (car (second ons))
|
d@177
|
697 (car (first ons)))
|
d@177
|
698 *rounding-factor*)
|
d@177
|
699 (null (cdr (first ons)))))
|
d@177
|
700 (if (<= (- (car (second ons))
|
d@177
|
701 (car (first ons)))
|
d@177
|
702 *rounding-factor*)
|
d@177
|
703 (progn
|
d@177
|
704 (push (cons (caar ons) (remove-duplicates (nconc (cdr (first ons))
|
d@177
|
705 (cdr (second ons)))))
|
d@177
|
706 new-ons)
|
d@177
|
707 (setf skip t))
|
d@177
|
708 (push (car ons) new-ons)))))))
|
d@177
|
709
|
d@166
|
710
|
d@166
|
711 (defun guess-rounding-factor (events)
|
d@166
|
712 "Assuming that only durations need quantising, look at the lcd for
|
d@166
|
713 onsets"
|
d@177
|
714 (let ((rounding-factor (/ 1 *rounding-factor*)))
|
d@177
|
715 (when events
|
d@177
|
716 (let ((crotchet (duration (crotchet (car events)))))
|
d@177
|
717 (do ((events events (cdr events)))
|
d@177
|
718 ((null (cdr events)))
|
d@177
|
719 (do ((divisor rounding-factor (* 2 divisor)))
|
d@177
|
720 ((<= (rem (* (timepoint (car events)) crotchet)
|
d@177
|
721 divisor)
|
d@177
|
722 (/ divisor 3))
|
d@177
|
723 (setf rounding-factor divisor))))))
|
d@177
|
724 (/ 1 rounding-factor)))
|
d@177
|
725
|
d@177
|
726 (defun guess-rounding-factor-smart (events)
|
d@177
|
727 "Assuming that only durations need quantising, look at the lcd for
|
d@177
|
728 onsets"
|
d@166
|
729 (let ((times (map 'list #'(lambda (x)
|
d@166
|
730 (denominator (* (timepoint x)
|
d@166
|
731 (duration (crotchet x)))))
|
d@166
|
732 events)))
|
d@166
|
733 (/ 1 (apply #'lcm times)))) |