1 (in-package :gsharp-measure
)
3 (defmacro defrclass
(name base slots
)
5 (define-stealth-mixin ,name
() ,base
6 ((modified-p :initform t
:accessor modified-p
)
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13 (defmethod more-sharps :after
((sig key-signature
) &optional n
)
15 (let ((staff (staff sig
)))
16 (invalidate-everything-using-staff (buffer staff
) staff
)))
18 (defmethod more-flats :after
((sig key-signature
) &optional n
)
20 (let ((staff (staff sig
)))
21 (invalidate-everything-using-staff (buffer staff
) staff
)))
23 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
27 (define-stealth-mixin rstaff
() staff
28 ((rank :accessor staff-rank
)))
30 (defun invalidate-slice-using-staff (slice staff
)
31 (declare (ignore staff
)) ; maybe use this later
32 (loop for bar in
(bars slice
)
33 do
(loop for element in
(elements bar
)
34 do
(mark-modified element
))))
36 (defun invalidate-everything-using-staff (buffer staff
)
37 (loop for segment in
(segments buffer
)
38 do
(loop for layer in
(layers segment
)
39 do
(when (member staff
(staves layer
))
40 (invalidate-slice-using-staff (head layer
) staff
)
41 (invalidate-slice-using-staff (body layer
) staff
)
42 (invalidate-slice-using-staff (tail layer
) staff
)))))
44 (defmethod (setf clef
) :before
(clef (staff staff
))
45 (invalidate-everything-using-staff (buffer staff
) staff
))
47 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
52 (;; The relative x offset of the accidental of the note with respect
53 ;; to the cluster. A value of nil indicates that accidental has
54 ;; not been placed yet
55 (final-relative-accidental-xoffset :initform nil
56 :accessor final-relative-accidental-xoffset
)
57 (final-accidental :initform nil
:accessor final-accidental
)
58 ;; the relative x offset of the note with respect to the cluster
59 (final-relative-note-xoffset :accessor final-relative-note-xoffset
)
60 ;; the absolute y position of any dot, or NIL if dots should not be
62 (final-absolute-dot-ypos :accessor final-absolute-dot-ypos
:initform nil
)
65 ;;; given a list of notes, group them so that every note in the group
66 ;;; is displayed on the same staff. Return the list of groups.
67 (defun group-notes-by-staff (notes)
70 (push (remove (staff (car notes
)) notes
:test-not
#'eq
:key
#'staff
) groups
)
71 (setf notes
(remove (staff (car notes
)) notes
:test
#'eq
:key
#'staff
)))
74 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
78 ;;; The relement class mixes into the element class. It adds
79 ;;; a `duration' slot that contains the duration of the element.
80 ;;; It also makes sure that whenever the duration of an element
81 ;;; is being asked for, the new value is computed should any
82 ;;; modification to the element have taken place in the meantime.
84 (defrclass relement element
85 ((duration :initform nil
)
86 (timeline :accessor timeline
)))
88 (defmethod duration :around
((element relement
))
89 (with-slots (duration) element
91 (setf duration
(call-next-method)))
94 (defmethod mark-modified ((element relement
))
95 (setf (modified-p element
) t
96 (slot-value element
'duration
) nil
)
98 (mark-modified (bar element
))))
100 (defmethod (setf notehead
) :after
(notehead (element relement
))
101 (declare (ignore notehead
))
102 (mark-modified element
))
104 (defmethod (setf rbeams
) :after
(rbeams (element relement
))
105 (declare (ignore rbeams
))
106 (mark-modified element
))
108 (defmethod (setf lbeams
) :after
(lbeams (element relement
))
109 (declare (ignore lbeams
))
110 (mark-modified element
))
112 (defmethod (setf dots
) :after
(dots (element relement
))
113 (declare (ignore dots
))
114 (mark-modified element
))
116 (defmethod (setf stem-direction
) :after
(direction (element relement
))
117 (declare (ignore direction
))
118 (mark-modified element
))
120 (defmethod (setf annotations
) :after
(annotations (element relement
))
121 (declare (ignore annotations
))
122 (mark-modified element
))
124 (defmethod append-char :after
((element lyrics-element
) char
)
125 (declare (ignore char
))
126 (mark-modified element
))
128 (defmethod note-position ((note note
))
129 (let ((clef (clef (staff note
))))
131 (bottom-line clef
))))
133 ;;; given a list of notes, return the one that is at the top
134 (defun top-note (notes)
135 (reduce (lambda (n1 n2
)
136 (cond ((< (staff-rank (staff n1
))
137 (staff-rank (staff n2
)))
139 ((> (staff-rank (staff n1
))
140 (staff-rank (staff n2
)))
142 ((> (note-position n1
)
148 ;;; given a list of notes, return the one that is at the bottom
149 (defun bot-note (notes)
150 (reduce (lambda (n1 n2
)
151 (cond ((> (staff-rank (staff n1
))
152 (staff-rank (staff n2
)))
154 ((< (staff-rank (staff n1
))
155 (staff-rank (staff n2
)))
157 ((< (note-position n1
)
163 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
167 (define-stealth-mixin rcluster
() cluster
168 ((final-stem-direction :accessor final-stem-direction
)
169 ;; the position, in staff steps, of the top note in the element.
170 (top-note-pos :accessor top-note-pos
)
171 ;; the position, in staff steps, of the bottom note in the element.
172 (bot-note-pos :accessor bot-note-pos
)))
174 ;;; Return true if and only if the element is a non-empty cluster
175 (defun non-empty-cluster-p (element)
176 (and (typep element
'cluster
)
177 (not (null (notes element
)))))
179 ;;; Compute and store some important information about a non-empty
181 ;;; * the position, in staff steps of the top note.
182 ;;; * the position, in staff steps of the bottom note.
183 (defun compute-top-bot-pos (cluster)
184 (assert (non-empty-cluster-p cluster
))
185 (setf (top-note-pos cluster
) (note-position (top-note (notes cluster
)))
186 (bot-note-pos cluster
) (note-position (bot-note (notes cluster
)))))
188 (defmethod add-note :after
((element relement
) (note note
))
189 (mark-modified element
))
191 (defmethod remove-note :before
((note rnote
))
193 (mark-modified (cluster note
))))
195 ;;; Given a non-empty cluster that is not beamed together with any
196 ;;; other clusters, compute and store its final stem direction.
197 (defun compute-final-stem-direction (cluster)
198 (assert (non-empty-cluster-p cluster
))
199 (setf (final-stem-direction cluster
)
200 (if (or (eq (stem-direction cluster
) :up
) (eq (stem-direction cluster
) :down
))
201 (stem-direction cluster
)
202 (let ((top-note-pos (top-note-pos cluster
))
203 (bot-note-pos (bot-note-pos cluster
)))
204 (if (>= (- top-note-pos
4)
209 ;;; Given a beam group containing at least two nonempty clusters,
210 ;;; compute and store the final stem directions of all the non-empty
211 ;;; clusters in the group
212 (defun compute-final-stem-directions (elements)
213 (let ((stem-direction (if (not (eq (stem-direction (car elements
)) :auto
))
214 (stem-direction (car elements
))
216 (loop for element in elements
217 when
(non-empty-cluster-p element
)
218 maximize
(top-note-pos element
)))
220 (loop for element in elements
221 when
(non-empty-cluster-p element
)
222 minimize
(bot-note-pos element
))))
223 (if (>= (- top-note-pos
4) (- 4 bot-note-pos
)) :down
:up
)))))
224 (loop for element in elements
225 when
(non-empty-cluster-p element
)
226 do
(setf (final-stem-direction element
) stem-direction
))))
228 (defun compute-final-dot-positions (group)
229 (setf group
(sort (copy-list group
) #'> :key
#'note-position
))
232 (let* ((position (note-position note
))
233 (ideal (if (oddp position
) position
(1+ position
))))
235 ;; if there's no dot at our ideal position, use that
236 ((not (member ideal so-far
)) (push (setf (final-absolute-dot-ypos note
) ideal
) so-far
))
237 ;; if the note in question is on a line and we haven't
238 ;; got a dot in the space underneath, use that
239 ((and (evenp position
) (not (member (- ideal
2) so-far
)))
240 (push (setf (final-absolute-dot-ypos note
) (- ideal
2)) so-far
))
241 ;; otherwise, give up for this note
242 (t (setf (final-absolute-dot-ypos note
) nil
)))))))
244 (defun find-prevailing-accidental (note)
245 (let* ((cluster (cluster note
))
246 ;; KLUDGE: This computation looks at the current layer's
247 ;; elements, and the note's key signature. While it's
248 ;; arguably right (in that accidentals in one layer don't
249 ;; affect accidentals in another) it's only arguable, and it
250 ;; would be nice if it weren't so unbelievably hard to do it
253 ;; FIXME: I can never remember how to access bar elements
254 ;; nicely, and here we need to access them in reverse
256 (index (position cluster
(elements bar
)))
257 (keysig (keysig note
)))
259 (loop for i downfrom
(1- index
) to
0
260 for element
= (elt (elements bar
) i
)
261 while
(gsharp::starts-before-p keysig bar element
)
264 (loop for n in
(notes element
)
265 when
(and (eq (staff n
) (staff note
))
266 (= (pitch n
) (pitch note
)))
267 do
(return-from find-prevailing-accidental
269 (aref (alterations keysig
) (mod (pitch note
) 7))))
271 ;;; Given a list of notes to be displayed on the same staff line, for
272 ;;; each note, compute the accidental to be displayed as a function of
273 ;;; the accidentals of the note and the key signature of the staff.
274 (defun compute-final-accidentals (group)
275 (loop for note in group do
276 (setf (final-accidental note
)
277 (if (eq (accidentals note
) (find-prevailing-accidental note
))
279 (accidentals note
)))))
281 (defmacro define-accidental-kerning
(left right table
)
282 `(let ((plist (getf (symbol-plist 'accidental-kerning
) ',right
)))
283 (setf (getf (symbol-plist 'accidental-kerning
) ',right
)
284 (cons (cons ',left
',table
)
285 (remove ',left plist
:key
#'car
)))))
286 (defmacro define-default-accidental-kerning
(right table
)
287 `(define-accidental-kerning default
,right
,table
))
289 (macrolet ((define-kernings (&rest args
)
290 `(progn ,@(loop for
(left right table
) on args by
#'cdddr
291 collect
`(define-accidental-kerning ,left
,right
,table
)))))
293 :double-flat
:notehead
#( 0 0 0 3.5 3.5 3.5 3.5 3.5 3.5 1 0)
294 :flat
:notehead
#( 0 0 0 3.5 3.5 3.5 3.5 3.5 3.5 1 0)
295 :natural
:notehead
#( 0 3.5 3.5 3.5 3.5 3.5 3.5 3.5 1 1 0)
296 :sharp
:notehead
#( 0 3.5 3.5 3.5 3.5 3.5 3.5 3.5 1 1 0)
297 :double-sharp
:notehead
#( 0 0 0 3.5 3.5 3.5 3.5 3.5 0 0 0)
299 :double-flat
:double-flat
#(3.8
3.8 3.8 3.8 3.8 3.8 3.8 3.8 3 3 0)
300 :flat
:double-flat
#(3.8
3.8 3.8 3.8 3.8 3.8 3.8 3.8 3 3 0)
301 :natural
:double-flat
#(3.8
3.8 3.8 3.8 3.8 3.8 3.8 3.8 3 3 0)
302 :sharp
:double-flat
#( 4 4 4 4 4 4 4 4 4 3.5 0)
303 :double-sharp
:double-flat
#(3.8
3.8 3.8 3.8 3.8 3.8 3.8 3.8 0 0 0)
305 :double-flat
:flat
#( 2 2 2 2 2 2 2 2 1.5 1 0)
306 :flat
:flat
#( 2 2 2 2 2 2 2 2 1.5 1 0)
307 :natural
:flat
#( 2 2 2 2 2 2 2 2 1.5 1 0)
308 :sharp
:flat
#(2.4
2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.4 1.5 0)
309 :double-sharp
:flat
#(2.4
2.4 2.4 2.4 2.4 2.4 2.4 2.4 0 0 0)
311 :double-flat
:natural
#( 2 2 2 2 2 2 2 2 2 1.5 1.5)
312 :flat
:natural
#( 2 2 2 2 2 2 2 2 2 1.5 1.5)
313 :natural
:natural
#( 2 2 2 2 2 2 2 2 2 1.5 1.5)
314 :sharp
:natural
#( 2 2 2 2 2 2 2 2 2 2 2)
315 :double-sharp
:natural
#( 2 2 2 2 2 2 2 2 1 1 1)
317 :double-flat
:sharp
#( 0 2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.0 1.5 1.0)
318 :flat
:sharp
#( 0 2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.0 1.5 1.0)
319 :natural
:sharp
#(2.4
2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.0 1.5 1.0)
320 :sharp
:sharp
#(2.4
2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.0)
321 :double-sharp
:sharp
#( 0 2.4 2.4 2.4 2.4 2.4 2.4 2.4 2.4 0 0)
323 :double-flat
:double-sharp
#( 0 0 2.4 2.4 2.4 2.4 2.4 2.4 2 1 0)
324 :flat
:double-sharp
#( 0 0 2.4 2.4 2.4 2.4 2.4 2.4 2 1 0)
325 :natural
:double-sharp
#( 0 0 2.4 2.4 2.4 2.4 2.4 2.4 2 1 0)
326 :sharp
:double-sharp
#( 0 2.8 2.8 2.8 2.8 2.8 2.8 2.8 2.8 2.8 0)
327 :double-sharp
:double-sharp
#( 0 0 0 2.8 2.8 2.8 2.8 2.8 0 0 0)
330 (defvar *default-accidental-kerning
*
331 #(4.0
4.0 4.0 4.0 4.0 4.0 4.0 4.0 4.0 4.0 4.0))
333 ;;; given 1) a type of accidental 2) its position (in staff steps) 3)
334 ;;; a type of accidental or a type of notehead, and 4) its position,
335 ;;; return the x offset of the first accidental, i.e., how many staff
336 ;;; steps to the left that it must be moved in order to avoid overlap
337 ;;; with the second one.
338 (defun accidental-distance (acc1 pos1 acc2 pos2
)
339 (let* ((dist (- pos2 pos1
))
340 (right-info (getf (symbol-plist 'accidental-kerning
) acc2
))
341 (left-right-info (cdr (assoc acc1 right-info
)))
342 (default-right-info (cdr (assoc 'default right-info
))))
345 ((or (not right-info
) (and (not left-right-info
) (not default-right-info
)))
346 (aref *default-accidental-kerning
* (+ dist
5)))
347 ((not left-right-info
) (aref default-right-info
(+ dist
5)))
348 (t (aref left-right-info
(+ dist
5))))))
350 ;;; given two notes (where the first one has an accidental, and the
351 ;;; second one may or may not have an accidental) and the conversion
352 ;;; factor between staff steps and x positions, compute the x offset
353 ;;; of the accidental of the first note. If the second note has
354 ;;; an accidental, but that has not been given a final x offset, then
355 ;;; use the x offset of the notehead instead.
356 (defun accidental-relative-xoffset (note1 note2 staff-step
)
357 (let* ((acc1 (final-accidental note1
))
358 (pos1 (note-position note1
))
359 (acc2 (if (and (final-accidental note2
)
360 (final-relative-accidental-xoffset note2
))
361 (final-accidental note2
)
363 (pos2 (note-position note2
))
364 (xpos2 (or (final-relative-accidental-xoffset note2
)
365 (final-relative-note-xoffset note2
))))
366 (- xpos2
(* staff-step
(accidental-distance acc1 pos1 acc2 pos2
)))))
368 ;;; given a note and a list of notes, compute x offset of the accidental
369 ;;; of the note as required by each of the notes in the list. In order
370 ;;; for the accidental of the note not to overlap any of the others,
371 ;;; we must use the minimum of all the x offsets thus computed.
372 (defun accidental-min-xoffset (note1 notes staff-step
)
373 (reduce #'min notes
:key
(lambda (note) (accidental-relative-xoffset note1 note staff-step
))))
375 ;;; given a list of notes that have accidentals to place, and a list of
376 ;;; notes that either have no accidentals or with already-placed accidentals,
377 ;;; compute the note in the first list that can be placed as far to the right
379 (defun best-accidental (notes-with-accidentals notes staff-step
)
380 (reduce (lambda (note1 note2
) (if (>= (accidental-min-xoffset note1 notes staff-step
)
381 (accidental-min-xoffset note2 notes staff-step
))
384 notes-with-accidentals
))
386 ;;; for each note in a list of notes, if it has an accidental, compute
387 ;;; the final relative x offset of that accidental and store it in the note.
388 (defun compute-final-relative-accidental-xoffset (notes final-stem-direction
)
389 (let* ((staff-step (score-pane:staff-step
1))
390 ;; sort the notes from top to bottom
391 (notes (sort (copy-list notes
)
392 (lambda (x y
) (> (note-position x
) (note-position y
)))))
393 (notes-with-accidentals (remove-if-not #'final-accidental notes
)))
394 ;; initially, no accidental has been placed
395 (loop for note in notes do
(setf (final-relative-accidental-xoffset note
) nil
))
396 (when (eq final-stem-direction
:up
)
397 ;; when the stem direction is :up and there is a suspended note
398 ;; i.e., one to the right of the stem, then the accidental of the topmost
399 ;; suspended note is placed first.
400 (let ((first-suspended-note
401 (find 0 notes-with-accidentals
:test
#'/= :key
#'final-relative-note-xoffset
)))
402 (when first-suspended-note
403 (setf notes-with-accidentals
404 (remove first-suspended-note notes-with-accidentals
))
405 (setf (final-relative-accidental-xoffset first-suspended-note
)
406 (accidental-min-xoffset first-suspended-note notes staff-step
)))))
407 ;; place remaining accidentals
408 (loop while notes-with-accidentals
409 do
(let ((choice (best-accidental notes-with-accidentals notes staff-step
)))
410 (setf notes-with-accidentals
411 (remove choice notes-with-accidentals
))
412 (setf (final-relative-accidental-xoffset choice
)
413 (accidental-min-xoffset choice notes staff-step
))))))
415 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
419 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
426 (defmethod add-element :after
((element element
) (bar rbar
) position
)
427 (declare (ignore position
))
430 (defmethod remove-element :before
((element element
) (bar rbar
))
433 (defmethod mark-modified ((bar rbar
))
434 (setf (modified-p bar
) t
)
436 (mark-modified (slice bar
))))
438 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
442 (defrclass rslice slice
445 (defmethod mark-modified ((slice rslice
))
446 (setf (modified-p slice
) t
)
448 (mark-modified (layer slice
))))
450 (defmethod add-bar :after
((bar bar
) (slice rslice
) position
)
451 (declare (ignore position
))
452 (mark-modified slice
))
454 (defmethod remove-bar :before
((bar rbar
))
456 (mark-modified (slice bar
))))
458 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
462 (defrclass rlayer layer
465 (defmethod mark-modified ((layer rlayer
))
466 (setf (modified-p layer
) t
)
467 (when (segment layer
)
468 (mark-modified (segment layer
))))
470 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
474 ;;; A timeline of a measure is the set of all simultaneous elements of
475 ;;; the bars of the meausure. The duration of a timeline is either
476 ;;; the temporal distance between it and the next closest timeline
477 ;;; following it, or, in case it is the last timeline of the measure,
478 ;;; the duration of the longest element of the timeline.
480 (defclass timeline
(flexichain:element-rank-mixin
)
481 ((start-time :initarg
:start-time
:reader start-time
)
482 (elements :initform
'() :accessor elements
)
483 (duration :initarg
:duration
:accessor duration
)
484 (elasticity :accessor elasticity
)
485 ;; the minimum x offset from this timeline to the next, or, if this
486 ;; is the last timeline, from this one to the end of the measure
487 (smallest-gap :initform
0 :accessor smallest-gap
)))
489 (defclass ranked-flexichain
(flexichain:standard-flexichain flexichain
:flexirank-mixin
)
492 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
496 ;;; A measure represents the set of simultaneous bars.
497 (defclass measure
(obseq-elem)
498 (;; the smallest duration of any timeline in the measure
499 (min-dist :initarg
:min-dist
:accessor measure-min-dist
)
500 ;; the coefficient of a measure is the sum of d_i^k where d_i
501 ;; is the duration of the i:th timeline, and k is the spacing style
502 (coeff :initarg
:coeff
:accessor measure-coeff
)
503 ;; the position of a measure in the sequence of measures
504 ;; of a buffer is indicated by two numbers, the position
505 ;; of the segment to which the measure belongs within the
506 ;; sequence of segments of the buffer, and the position of
507 ;; the bars within that segment.
508 (seg-pos :initarg
:seg-pos
:reader measure-seg-pos
)
509 (bar-pos :initarg
:bar-pos
:reader measure-bar-pos
)
510 ;; a list of the bars that make up this measure
511 (bars :initarg
:bars
:reader measure-bars
)
512 ;; a ranked flexichain of timelines
513 (timelines :initform
(make-instance 'ranked-flexichain
) :reader timelines
)
514 ;; a convex piecewise-linear function that determines the
515 ;; horizontal size of the measure as a function of the "force" that
517 (elasticity-function :accessor elasticity-function
)))
519 (defun make-measure (seg-pos bar-pos bars
)
520 (make-instance 'measure
:seg-pos seg-pos
:bar-pos bar-pos
:bars bars
))
522 (defmethod print-object ((obj measure
) stream
)
523 (with-slots (min-dist coeff seg-pos bar-pos
) obj
524 (print-unreadable-object (obj stream
:identity t
:type t
)
525 (format stream
"(~D, ~D) @ ~D-~D" min-dist coeff seg-pos bar-pos
))))
527 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
531 (defrclass rsegment segment
532 ((measures :initform
'() :reader measures
)))
534 (defmethod mark-modified ((segment rsegment
))
535 (setf (modified-p segment
) t
)
536 (when (buffer segment
)
537 (mark-modified (buffer segment
))))
539 (defmethod add-layer :after
((layer layer
) (segment rsegment
))
540 (mark-modified segment
))
542 (defmethod remove-layer :before
((layer rlayer
))
543 (when (segment layer
)
544 (mark-modified (segment layer
))))
546 (defun adjust-lowpos-highpos (segment)
547 (when (modified-p segment
)
548 (let ((buffer (buffer segment
)))
549 ;; Do this better. Now, we essentially tell the obseq library
550 ;; that every measure in the entire buffer has been damaged.
551 (obseq-first-undamaged-element buffer nil
)
552 (obseq-last-undamaged-element buffer nil
))))
554 (defmethod measures :before
((segment rsegment
))
555 (when (modified-p segment
)
556 (let ((spacing-style (spacing-style (buffer-cost-method (buffer segment
)))))
557 (compute-measures segment
)
558 ;; avoid an infinite computation by using slot-value here
559 (loop for measure in
(slot-value segment
'measures
)
560 do
(compute-timelines measure spacing-style
)))
561 (setf (modified-p segment
) nil
)))
563 (defmethod nb-measures ((segment rsegment
))
564 (length (measures segment
)))
566 ;;; Given a segment and a position, return the measure in that
567 ;;; position in the sequence of measures in the segment.
568 (defmethod measureno ((segment rsegment
) position
)
569 (elt (measures segment
) position
))
571 ;;; Given a group of notes (i.e. a list of notes, all displayed on the
572 ;;; same staff, compute their final x offsets. This is a question of
573 ;;; determining whether the note goes to the right or to the left of
574 ;;; the stem. The head-note of the stem goes to the left of an
575 ;;; up-stem and to the right of a down-stem. The x offset of a cluster
576 ;;; gives the x position of the head-note.
577 (defun compute-final-relative-note-xoffsets (group direction
)
578 (setf group
(sort (copy-list group
)
579 (if (eq direction
:up
)
580 (lambda (x y
) (< (note-position x
) (note-position y
)))
581 (lambda (x y
) (> (note-position x
) (note-position y
))))))
582 (score-pane:with-suspended-note-offset offset
583 ;; the first element of the group is the head-note
584 (setf (final-relative-note-xoffset (car group
)) 0)
585 ;; OFFSET is a positive quantity that determines the
586 ;; absolute difference between the x offset of a suspended
587 ;; note and that of a normally positioned note.
588 (when (eq direction
:down
) (setf offset
(- offset
)))
589 (loop for note in
(cdr group
)
590 and old-note
= (car group
) then note
591 do
(let* ((pos (note-position note
))
592 (old-pos (note-position old-note
))
593 ;; if adjacent notes are just one staff step apart,
594 ;; then one must be suspended.
595 (dx (if (= (abs (- pos old-pos
)) 1) offset
0)))
596 (setf (final-relative-note-xoffset note
) dx
)
597 ;; go back to ordinary offset
598 (when (= (abs (- pos old-pos
)) 1)
599 (setf note old-note
))))))
601 (defun compute-staff-group-parameters (staff-group stem-direction
)
602 (compute-final-relative-note-xoffsets staff-group stem-direction
)
603 (compute-final-dot-positions staff-group
)
604 (compute-final-accidentals staff-group
)
605 (compute-final-relative-accidental-xoffset staff-group stem-direction
))
607 ;;; compute some important parameters of an element
608 (defgeneric compute-element-parameters
(element))
610 (defmethod compute-element-parameters (element)
613 (defmethod compute-element-parameters ((element cluster
))
614 (when (non-empty-cluster-p element
)
615 (compute-top-bot-pos element
)
616 (loop for staff-group in
(group-notes-by-staff (notes element
))
617 do
(compute-staff-group-parameters staff-group
(final-stem-direction element
)))))
619 (defun compute-beam-group-parameters (elements)
620 (loop for element in elements
621 do
(when (modified-p element
)
622 (when (non-empty-cluster-p element
)
623 (compute-top-bot-pos element
))))
624 (if (null (cdr elements
))
625 (when (non-empty-cluster-p (car elements
))
626 (compute-final-stem-direction (car elements
)))
627 (compute-final-stem-directions elements
))
628 (loop for element in elements
629 do
(compute-element-parameters element
)
630 do
(setf (modified-p element
) nil
)))
632 ;;; Given a list of the elements of a bar, return a list of beam
633 ;;; groups. A beam group is defined to be either a singleton list or
634 ;;; a list with more than one element. In the case of a singleton,
635 ;;; the element is either a non-cluster, an empty cluster, a cluster
636 ;;; that does not beam to the right, or a cluster that does beam to
637 ;;; the right, but either it is the last cluster in the bar, or the
638 ;;; first following cluster in the bar does not beam to the left. In
639 ;;; the case of a list with more than one element, the first element
640 ;;; is a cluster that beams to the right, the last element is a
641 ;;; cluster that beams to the left, and all other clusters in the list
642 ;;; beam both to the left and to the right. Notice that in the last
643 ;;; case, elements other than the first and the last can be
644 ;;; non-clusters, or empty clusters.
645 (defun beam-groups (elements)
647 (loop until
(null elements
) do
648 (setf group
(list (car elements
))
649 elements
(cdr elements
))
650 (when (and (non-empty-cluster-p (car group
))
651 (plusp (rbeams (car group
))))
652 (loop while
(and (not (null elements
))
653 (or (not (typep (car elements
) 'cluster
))
654 (null (notes (car elements
)))
655 (plusp (lbeams (car elements
)))))
656 do
(push (pop elements
) group
)
657 until
(and (non-empty-cluster-p (car group
))
658 (zerop (rbeams (car group
)))))
659 ;; pop off trailing unbeamable objects
660 (loop until
(non-empty-cluster-p (car group
))
661 do
(push (pop group
) elements
)))
662 collect
(nreverse group
))))
664 ;;; compute some important parameters of a bar
665 (defgeneric compute-bar-parameters
(bar))
667 (defmethod compute-bar-parameters (bar)
670 (defmethod compute-bar-parameters ((bar melody-bar
))
671 (loop for group in
(beam-groups (elements bar
))
672 do
(compute-beam-group-parameters group
)))
674 ;;; From a list of simultaneous bars (and some other stuff), create a
675 ;;; measure. The `other stuff' is the spacing style, which is needed
676 ;;; in order to compute the coefficient of the measure, the position
677 ;;; of the segment to which the bars belong in the sequence of
678 ;;; segments of the buffer, and the position of the bars in the
679 ;;; sequence of bars within that segment. The last two items are used
680 ;;; to indicate the position of the measure in the sequence of all
681 ;;; measures of the buffer.
682 (defun compute-measure (bars seg-pos bar-pos buffer
)
683 (score-pane:with-staff-size
(gsharp-buffer::rastral-size buffer
)
684 (loop for bar in bars
685 do
(when (modified-p bar
)
686 (compute-bar-parameters bar
)
687 (setf (modified-p bar
) nil
)))
688 (make-measure seg-pos bar-pos bars
)))
690 (defun compute-timelines (measure spacing-style
)
691 (let ((timelines (timelines measure
)))
692 (flet ((compute-bar-timelines (bar)
693 (loop with timeline-index
= 0
694 for element in
(elements bar
)
695 and start-time
= 0 then
(+ start-time
(duration element
))
696 do
(loop until
(= timeline-index
(flexichain:nb-elements timelines
))
697 for timeline
= (flexichain:element
* timelines timeline-index
)
698 until
(or (> (start-time timeline
) start-time
)
699 (and (= (start-time timeline
) start-time
)
700 (or (zerop (duration element
))
701 ;; either none or every element of a timline
702 ;; has zero duration, so we only have to test
704 (not (zerop (duration (car (elements timeline
))))))))
705 do
(incf timeline-index
))
706 do
(when (or (= timeline-index
(flexichain:nb-elements timelines
))
707 (let ((timeline (flexichain:element
* timelines timeline-index
)))
708 (or (> (start-time timeline
) start-time
)
709 (and (zerop (duration element
))
710 (not (zerop (duration (car (elements timeline
)))))))))
711 (let ((timeline (make-instance 'timeline
712 :start-time start-time
)))
713 (flexichain:insert
* timelines timeline-index timeline
)))
714 do
(let ((timeline (flexichain:element
* timelines timeline-index
)))
715 (push element
(elements timeline
))
716 (setf (timeline element
) timeline
)
717 (incf timeline-index
)))))
718 (loop for bar in
(measure-bars measure
)
719 do
(compute-bar-timelines bar
)))
720 ;; compute the duration of each timeline except the last one
721 (loop for i from
0 below
(1- (flexichain:nb-elements timelines
))
722 do
(setf (duration (flexichain:element
* timelines i
))
723 (- (start-time (flexichain:element
* timelines
(1+ i
)))
724 (start-time (flexichain:element
* timelines i
)))))
725 ;; compute the duration of the last timeline, if any
726 (unless (zerop (flexichain:nb-elements timelines
))
727 (let ((measure-duration (reduce #'max
(measure-bars measure
) :key
#'duration
))
728 (last-timeline (flexichain:element
* timelines
(1- (flexichain:nb-elements timelines
)))))
729 (setf (duration last-timeline
) (- measure-duration
(start-time last-timeline
)))))
730 ;; set the coefficient and the min-dist of the measure
731 (loop with min-dist
= 10000
732 for timeline-index from
0 below
(flexichain:nb-elements timelines
)
733 for duration
= (duration (flexichain:element
* timelines timeline-index
))
734 sum
(expt duration spacing-style
) into coeff
735 do
(when (plusp duration
) (setf min-dist
(min min-dist duration
)))
736 ;; timelines with zero duration do not intervene in the calculation
738 finally
(setf (measure-coeff measure
) coeff
739 (measure-min-dist measure
) min-dist
))))
741 ;;; Compute all the measures of a segment by stepping through all the
742 ;;; bars in parallel as long as there is at least one simultaneous bar.
743 (defun compute-measures (segment)
744 (let ((buffer (buffer segment
)))
745 (setf (slot-value segment
'measures
)
746 (loop for all-bars on
(mapcar (lambda (layer) (bars (body layer
)))
748 by
(lambda (bars) (mapcar #'cdr bars
))
749 as bar-pos from
0 by
1
750 while
(notevery #'null all-bars
)
751 collect
(compute-measure
752 (remove nil
(mapcar #'car all-bars
))
753 (number segment
) bar-pos buffer
)))))
755 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
759 (define-stealth-mixin rbuffer
(obseq) buffer
760 ((modified-p :initform t
:accessor modified-p
)))
762 ;;; Given a buffer, a position of a segment in the sequence of
763 ;;; segments of the buffer, and a position of a measure within that
764 ;;; segment, return the corresponding measure.
765 (defmethod buffer-pos ((buffer rbuffer
) seg-pos bar-pos
)
766 (if (or (<= seg-pos -
1) (>= seg-pos
(nb-segments buffer
)))
768 (measureno (segmentno buffer seg-pos
) bar-pos
)))
770 ;;; as required by the obseq library, we supply a method on this
771 ;;; generic function. When we are given a measure other than the last
772 ;;; one in the segment, return the next one in the segment. When we
773 ;;; are given the last measure in a segment which is not the last one,
774 ;;; return the first measure in the following segment. When we are
775 ;;; given the last measure of the last segment, return nil as required
776 ;;; by the obseq library.
777 (defmethod obseq-next ((buf buffer
) (measure measure
))
778 (let ((seg-pos (measure-seg-pos measure
))
779 (bar-pos (measure-bar-pos measure
)))
780 (cond ((< (1+ bar-pos
) (nb-measures (segmentno buf seg-pos
)))
781 (buffer-pos buf seg-pos
(1+ bar-pos
)))
782 ((< (1+ seg-pos
) (nb-segments buf
))
783 (buffer-pos buf
(1+ seg-pos
) 0))
786 ;;; as required by the obseq library, we supply a method on this
787 ;;; generic function specialized on NIL, for which the first measure
788 ;;; of the first segment is returned.
789 (defmethod obseq-next ((buf buffer
) (measure (eql nil
)))
790 (measureno (segmentno buf
0) 0))
792 ;;; as required by the obseq library, we supply a method on this
793 ;;; generic function. When we are given a measure other than the first
794 ;;; one in the segment, return the previous one in the segment. When we
795 ;;; are given the first measure in a segment which is not the first one,
796 ;;; return the last measure in the preceding segment. When we are
797 ;;; given the first measure of the first segment, return nil as required
798 ;;; by the obseq library.
799 (defmethod obseq-prev ((buf buffer
) (measure measure
))
800 (let ((seg-pos (measure-seg-pos measure
))
801 (bar-pos (measure-bar-pos measure
)))
802 (cond ((> bar-pos
0) (buffer-pos buf seg-pos
(1- bar-pos
)))
803 ((> seg-pos
0) (buffer-pos buf
805 (1- (nb-measures (segmentno buf
(1- seg-pos
))))))
808 ;;; as required by the obseq library, we supply a method on this
809 ;;; generic function specialized on NIL, for which the last measure
810 ;;; of the last segment is returned.
811 (defmethod obseq-prev ((buf buffer
) (measure (eql nil
)))
813 (1- (nb-segments buf
))
814 (1- (nb-measures (segmentno buf
(1- (nb-segments buf
)))))))
816 (defmethod mark-modified ((buffer rbuffer
))
817 (setf (modified-p buffer
) t
)
818 (setf (needs-saving buffer
) t
))
820 (defmethod add-segment :after
((segment segment
) (buffer rbuffer
) position
)
821 (declare (ignore position
))
822 (mark-modified buffer
))
824 (defmethod remove-segment :before
((segment rsegment
))
825 (when (buffer segment
)
826 (mark-modified (buffer segment
))))
828 (defparameter *staves-per-page
* 12)
829 (defgeneric systems-per-page
(buffer)
831 (let ((stave-count (length (staves b
))))
832 (assert (<= stave-count
*staves-per-page
*))
833 (floor *staves-per-page
* stave-count
))))
836 ;;; call fun on every list of measures (which make up a line)
838 (defun new-map-over-obseq-subsequences (fun buf
)
839 (loop with m
= (obseq-interval buf
(buffer-pos buf
0 0))
841 do
(multiple-value-bind (left right
)
842 ;; find the end points of the interval that contains m
843 (obseq-interval buf m
)
844 (funcall fun
(loop for mm
= left then
(obseq-next buf mm
)
846 until
(eq mm right
)))
847 ;; move to the next measure after the rightmost one
848 ;; in the current line
849 (setf m
(obseq-next buf right
)))))
851 (defun buffer-cost-method (buffer)
852 (obseq-cost-method buffer
))
854 (defmethod recompute-measures ((buffer rbuffer
))
855 (when (modified-p buffer
)
857 (loop for staff in
(staves buffer
)
859 do
(setf (staff-rank staff
) i
))
860 ;; for now, invalidate everything
861 (mapc #'adjust-lowpos-highpos
(segments buffer
))
862 ;; initialize cost method from buffer-specific style parameters
863 (setf (obseq-cost-method buffer
)
864 (make-measure-cost-method
865 (min-width buffer
) (spacing-style buffer
)
866 (- (right-edge buffer
) (left-margin buffer
) (left-offset buffer
))
867 (systems-per-page buffer
)))
869 (setf (modified-p buffer
) nil
)))
871 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
875 ;;; As required by the obseq library, define a cost method
876 ;;; that is passed to the cost-comparison methods.
877 (defclass measure-cost-method
(cost-method)
878 (;; the min width is taken from the min width of the buffer
879 (min-width :initarg
:min-width
:reader min-width
)
880 ;; the spaceing style is taken from the spacing style of the buffer
881 (spacing-style :initarg
:spacing-style
:reader spacing-style
)
882 ;; the amount of horizontal space available to music material
883 (line-width :initarg
:line-width
:reader line-width
)
884 ;; number of lines that will fit on a page
885 (lines-per-page :initarg
:lines-per-page
:reader lines-per-page
)))
887 (defun make-measure-cost-method (min-width spacing-style line-width lines-per-page
)
888 (make-instance 'measure-cost-method
890 :spacing-style spacing-style
891 :line-width line-width
892 :lines-per-page lines-per-page
))
894 ;;; As required by the obseq library, define a sequence cost, i.e., in
895 ;;; this case the cost of a sequence of measures.
896 (defclass measure-seq-cost
(seq-cost)
897 ((min-dist :initarg
:min-dist
:reader min-dist
)
898 (coeff :initarg
:coeff
:reader coeff
)
899 (nb-measures :initarg
:nb-measures
:reader nb-measures
)))
901 ;;; As required by the obseq library, define a total cost, i.e., in
902 ;;; this case the cost of a sequece of sequences of measures.
903 (defclass measure-total-cost
(total-cost)
904 ((cost :initarg
:cost
:reader measure-total-cost
)))
906 (defun make-measure-total-cost (cost)
907 (make-instance 'measure-total-cost
:cost cost
))
909 (defmethod print-object ((obj measure-total-cost
) stream
)
910 (print-unreadable-object (obj stream
:identity t
:type t
)
911 (format stream
"~D" (measure-total-cost obj
))))
913 ;;; As required by the obseq library, this method computes the
914 ;;; combined cost of a sequence of measures by taking the existing
915 ;;; cost of all but the last measures and combining it with the
916 ;;; characteristics of the last measure. The result is a sequence
917 ;;; cost that has the sum of the coefficients of each measure in the
918 ;;; sequence, the min of the min-dists of each measure in the
919 ;;; sequence, and the total number of measures in the sequence.
920 ;;; As far as Gsharp is concerned, this cost computation is
921 ;;; commutable, so rely on Obseq to supply the symmetric method.
922 (defmethod combine-cost ((method measure-cost-method
)
923 (seq-cost measure-seq-cost
)
925 (make-instance 'measure-seq-cost
926 :coeff
(+ (coeff seq-cost
) (measure-coeff elem
))
927 :min-dist
(min (min-dist seq-cost
) (measure-min-dist elem
))
928 :nb-measures
(1+ (nb-measures seq-cost
))))
930 ;;; As required by the obseq library, this method computes the
931 ;;; combined cost of a sequence of sequences of measures by taking the
932 ;;; existing cost of all but the last sequences of measures and
933 ;;; combining it with the sequence cost of the last sequence of
934 ;;; measures. The result is a total cost that has the max of the cost
935 ;;; of each individual sequence of measures. The reason for using the
936 ;;; max is that we do not want for a good line to be able to
937 ;;; compensate for the badness of another. We thus compute the score
938 ;;; that minimizes the maximum of the badness of each line. As far as
939 ;;; Gsharp is concerned, this cost computation is commutable, so rely
940 ;;; on Obseq to supply the symmetric method.
941 (defmethod combine-cost ((method measure-cost-method
)
942 (tcost measure-total-cost
)
943 (seq-cost measure-seq-cost
))
944 (make-instance 'measure-total-cost
945 :cost
(max (measure-total-cost tcost
)
946 (measure-seq-cost method seq-cost
))))
948 (defmethod combine-cost ((method measure-cost-method
)
949 (seq-cost measure-seq-cost
)
951 (make-instance 'measure-total-cost
952 :cost
(measure-seq-cost method seq-cost
)))
955 ;;; As required by the obseq library, this method computes the
956 ;;; sequence cost of a singleton sequence.
957 (defmethod combine-cost ((method measure-cost-method
)
959 (whatever (eql nil
)))
960 (make-instance 'measure-seq-cost
961 :coeff
(measure-coeff elem
)
962 :min-dist
(measure-min-dist elem
)
965 ;;; As required by the obseq library, this method computes the
966 ;;; sequence cost of a singleton sequence.
967 (defmethod combine-cost ((method measure-cost-method
)
970 (combine-cost method elem nil
))
972 ;;; The reduced width of a sequence of measures is the sum of the
973 ;;; widths of the measures in the sequence, but ignoring the space
974 ;;; before first timeline. If the min-dist is 0 (which I think is the
975 ;;; case if each measure has no timelines), then the reduced width is
976 ;;; 0, otherwise we obtain the reduced width by multiplying the sum of
977 ;;; the coefficients of each mesure in the sequence, the min-width to
978 ;;; use for the display, and (1/d_min)^k, where d_min is the duration
979 ;;; of the shortest timeline, and k is the spacing style.
980 (defmethod reduced-width ((method measure-cost-method
)
981 (seq-cost measure-seq-cost
))
982 (if (zerop (min-dist seq-cost
))
984 (* (coeff seq-cost
) (min-width method
)
985 (expt (/ (min-dist seq-cost
)) (spacing-style method
)))))
987 ;;; The natural width of a sequence of mesures is like the reduced
988 ;;; width, except that we do not ignore the space before the first
989 ;;; timeline in each measure. That space might be necessary to
990 ;;; parameterize one day, but for now we just use the w_min.
991 (defmethod natural-width ((method measure-cost-method
)
992 (seq-cost measure-seq-cost
))
993 (+ (reduced-width method seq-cost
)
994 (* (nb-measures seq-cost
) (min-width method
))))
996 ;;; The compress factor indicates how by how much a sequence of
997 ;;; measures must be compressed in order to fit the width at our
998 ;;; disposal. Values > 1 indicate that the sequence of mesures must
999 ;;; be stretched instead of compressed.
1000 (defmethod compress-factor ((method measure-cost-method
)
1001 (seq-cost measure-seq-cost
))
1002 (/ (natural-width method seq-cost
)
1003 (* (line-width method
) (lines-per-page method
))))
1005 ;;; As far as Gsharp is concerned, we define the cost of a sequence of
1006 ;;; measures as the max of the compress factor and its inverse. In
1007 ;;; other words, we consider it as bad to have to stretch a sequence by x%
1008 ;;; as it is to have to compress it by x%, and the more we have to
1009 ;;; compress or expand it, the worse it is. This way of doing it is
1010 ;;; not great. At some point, we need to severely penalize compressed
1011 ;;; sequences that become too short to display without overlaps, unless
1012 ;;; the sequence contains a single measure, of course.
1013 (defmethod measure-seq-cost ((method measure-cost-method
)
1014 (seq-cost measure-seq-cost
))
1015 (let ((c (compress-factor method seq-cost
)))
1018 ;;; As required by the obseq library, we define a method that
1019 ;;; determines whether we can prove that adding another measure to an
1020 ;;; existing sequence is guaranteed to make the cost of the sequence
1021 ;;; higher. The obseq library uses this to radically diminish the
1022 ;;; complexity of the computation.
1023 (defmethod seq-cost-cannot-decrease ((method measure-cost-method
)
1024 (seq-cost measure-seq-cost
))
1025 (>= (natural-width method seq-cost
)
1026 (* (line-width method
) (lines-per-page method
))))
1028 ;;; Compare the cost of two sequences of measures
1029 (defmethod cost-less ((method measure-cost-method
)
1030 (c1 measure-seq-cost
)
1031 (c2 measure-seq-cost
))
1032 (< (measure-seq-cost method c1
) (measure-seq-cost method c2
)))
1034 ;;; Compare the cost of two sequences of sequences of measures
1035 (defmethod cost-less ((method measure-cost-method
)
1036 (c1 measure-total-cost
)
1037 (c2 measure-total-cost
))
1038 (< (measure-total-cost c1
) (measure-total-cost c2
)))