Basic undo implemented for simple operations.
[gsharp.git] / measure.lisp
blobb5a3b0f29e3ff9039ddb83fa7c8dcc9748035ceb
1 (in-package :gsharp-measure)
3 (defmacro defrclass (name base slots)
4 `(progn
5 (define-stealth-mixin ,name () ,base
6 ((modified-p :initform t :accessor modified-p)
7 ,@slots))))
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
10 ;;;
11 ;;; Key signature
13 (defmethod more-sharps :after ((sig key-signature) &optional n)
14 (declare (ignore n))
15 (let ((staff (staff sig)))
16 (invalidate-everything-using-staff (buffer staff) staff)))
18 (defmethod more-flats :after ((sig key-signature) &optional n)
19 (declare (ignore n))
20 (let ((staff (staff sig)))
21 (invalidate-everything-using-staff (buffer staff) staff)))
23 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24 ;;;
25 ;;; Staff
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
48 ;;;
49 ;;; Note
51 (defrclass rnote note
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
61 ;; drawn
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)
68 (let ((groups '()))
69 (loop while notes do
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)))
72 groups))
74 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
75 ;;;
76 ;;; Element
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
90 (when (null duration)
91 (setf duration (call-next-method)))
92 duration))
94 (defmethod mark-modified ((element relement))
95 (setf (modified-p element) t
96 (slot-value element 'duration) nil)
97 (when (bar element)
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))))
130 (- (pitch 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)
143 (note-position n2))
145 (t n2)))
146 notes))
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)
158 (note-position n2))
160 (t n2)))
161 notes))
163 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
165 ;;; Cluster
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
180 ;;; cluster:
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))
192 (when (cluster note)
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)
205 (- 4 bot-note-pos))
206 :down
207 :up)))))
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))
215 (let ((top-note-pos
216 (loop for element in elements
217 when (non-empty-cluster-p element)
218 maximize (top-note-pos element)))
219 (bot-note-pos
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))
230 (let ((so-far nil))
231 (dolist (note group)
232 (let* ((position (note-position note))
233 (ideal (if (oddp position) position (1+ position))))
234 (cond
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
251 ;; the other way.
252 (bar (bar cluster))
253 ;; FIXME: I can never remember how to access bar elements
254 ;; nicely, and here we need to access them in reverse
255 ;; order...
256 (index (position cluster (elements bar)))
257 (keysig (keysig note)))
258 (assert index)
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)
262 do (typecase element
263 (cluster
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
268 (accidentals n))))))
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)))))
292 (define-kernings
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))))
343 (cond
344 ((> (abs dist) 5) 0)
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)
362 :notehead))
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
378 ;;; as possible.
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))
382 note1
383 note2))
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
417 ;;; Rest
419 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
421 ;;; Bar
423 (defrclass rbar bar
426 (defmethod add-element :after ((element element) (bar rbar) position)
427 (declare (ignore position))
428 (mark-modified bar))
430 (defmethod remove-element :before ((element element) (bar rbar))
431 (mark-modified bar))
433 (defmethod mark-modified ((bar rbar))
434 (setf (modified-p bar) t)
435 (when (slice bar)
436 (mark-modified (slice bar))))
438 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
440 ;;; Slice
442 (defrclass rslice slice
445 (defmethod mark-modified ((slice rslice))
446 (setf (modified-p slice) t)
447 (when (layer slice)
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))
455 (when (slice bar)
456 (mark-modified (slice bar))))
458 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
460 ;;; Layer
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
472 ;;; Timeline
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
494 ;;; Measure
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
516 ;; is applied to it
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
529 ;;; Segment
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)
611 nil)
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)
646 (let ((group '()))
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)
668 nil)
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
703 ;; the first one.
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
737 ;; of the min-dist
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)))
747 (layers segment))
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
757 ;;; Buffer
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))
784 (t nil))))
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
804 (1- seg-pos)
805 (1- (nb-measures (segmentno buf (1- seg-pos))))))
806 (t nil))))
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)))
812 (buffer-pos buf
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)
830 (:method (b)
831 (let ((stave-count (length (staves b))))
832 (assert (<= stave-count *staves-per-page*))
833 (floor *staves-per-page* stave-count))))
835 ;;; temporary stuff
836 ;;; call fun on every list of measures (which make up a line)
837 ;;; in the buffer
838 (defun new-map-over-obseq-subsequences (fun buf)
839 (loop with m = (obseq-interval buf (buffer-pos buf 0 0))
840 while m
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)
845 collect 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)
856 ;; number the staves
857 (loop for staff in (staves buffer)
858 for i from 0
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)))
868 (obseq-solve buffer)
869 (setf (modified-p buffer) nil)))
871 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
873 ;;; Cost functions
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
889 :min-width min-width
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)
924 (elem measure))
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)
950 (elem (eql nil)))
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)
958 (elem measure)
959 (whatever (eql nil)))
960 (make-instance 'measure-seq-cost
961 :coeff (measure-coeff elem)
962 :min-dist (measure-min-dist elem)
963 :nb-measures 1))
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)
968 (whatever (eql nil))
969 (elem measure))
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)))
1016 (max c (/ c))))
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)))