Add images/icon buttons for CLIM
[gsharp.git] / drawing.lisp
blobb4fef610ce090d5d59e6d2ada3b9fac8974c0bcb
1 (in-package :gsharp-drawing)
3 (defclass x-y-width-mixin ()
4 (;; indicates the absolute y position of the system to which the
5 ;; object belongs
6 (system-y-position :accessor system-y-position)
7 ;; the absolute x position of the object
8 (final-absolute-measure-xoffset :accessor final-absolute-measure-xoffset)
9 (final-width :accessor final-width)))
11 (define-stealth-mixin dbar (x-y-width-mixin) bar
12 ())
14 (define-stealth-mixin dmeasure (x-y-width-mixin) measure
15 (;; an elasticity function that describes how the space right after
16 ;; the initial barline of the measure behaves as a function of the
17 ;; force that is applied to it.
18 (prefix-elasticity-function :accessor prefix-elasticity-function)))
20 (define-stealth-mixin dstaff () staff
21 ((yoffset :initform 0 :accessor staff-yoffset)))
23 (define-presentation-method present
24 (object (type score-pane:clef) stream (view textual-view) &key)
25 (format stream "[~a clef on staff step ~a]" (name object) (lineno object)))
27 (define-presentation-method present
28 (object (type score-pane:fiveline-staff) stream (view textual-view) &key)
29 (format stream "[fiveline staff ~a]" (name object)))
31 (define-presentation-method present
32 (object (type score-pane:lyrics-staff) stream (view textual-view) &key)
33 (format stream "[lyrics staff ~a]" (name object)))
35 (defun key-signature-for-staff (staff measures)
36 (let ((key-signatures (key-signatures staff))
37 (barno (gsharp-numbering:number (car (measure-bars (car measures))))))
38 (or (and key-signatures
39 (find barno key-signatures :from-end t :test #'>
40 :key (lambda (x) (gsharp-numbering:number (bar x)))))
41 (keysig staff))))
43 (defmethod draw-staff-and-clef (pane (staff fiveline-staff) measures x1 x2)
44 (when (clef staff)
45 (present (clef staff)
46 `((score-pane:clef)
47 :name ,(name (clef staff))
48 :x ,(+ x1 10)
49 :staff-step ,(lineno (clef staff)))
50 :stream pane)
51 (let ((keysig (key-signature-for-staff staff measures)))
52 (let ((yoffset (b-position (clef staff))))
53 (loop for pitch in '(6 2 5 1 4 0 3)
54 for line in '(0 3 -1 2 -2 1 -3)
55 for x from (+ x1 10 (score-pane:staff-step 8)) by (score-pane:staff-step 2)
56 while (eq (aref (alterations keysig) pitch) :flat)
57 do (score-pane:draw-accidental pane :flat x (+ line yoffset))))
58 (let ((yoffset (f-position (clef staff))))
59 (loop for pitch in '(3 0 4 1 5 2 6)
60 for line in '(0 -3 1 -2 -5 -1 -4)
61 for x from (+ x1 10 (score-pane:staff-step 8)) by (score-pane:staff-step 2.5)
62 while (eq (aref (alterations keysig) pitch) :sharp)
63 do (score-pane:draw-accidental pane :sharp x (+ line yoffset)))))
64 (present staff
65 `((score-pane:fiveline-staff)
66 :x1 ,x1 :x2 ,x2)
67 :stream pane)))
69 (defmethod draw-staff-and-clef (pane (staff lyrics-staff) measures x1 x2)
70 (present staff
71 `((score-pane:lyrics-staff)
72 :x1 ,x1 :x2 ,x2)
73 :stream pane))
75 ;;; Return the final absolute x offset of a note. This value is
76 ;;; computed from the x offset of the cluster of the note and the
77 ;;; relative x offset of the note with respect to the cluster.
78 (defun final-absolute-note-xoffset (note)
79 (+ (final-absolute-element-xoffset (cluster note)) (final-relative-note-xoffset note)))
81 ;;; Return the final absolute x offset of the accidental of a note.
82 ;;; This value is computed from the x offset of the cluster of the
83 ;;; note and the relative x offset of the accidental of the note with
84 ;;; respect to the cluster.
85 (defun final-absolute-accidental-xoffset (note)
86 (+ (final-absolute-element-xoffset (cluster note)) (final-relative-accidental-xoffset note)))
88 (defun final-absolute-dot-xoffset (cluster)
89 (+ (final-absolute-element-xoffset cluster) (score-pane:staff-step (final-relative-dot-xoffset cluster))))
91 (defvar *cursor* nil)
93 ;;; Compute the elasticity of each timeline in each measure of the
94 ;;; measures of a system (line) by taking its duration to the power of
95 ;;; the spaceing style. This metric is arbitrarily normalized to the
96 ;;; duration of a whole note, which means that the force to apply to a
97 ;;; line is not comparable between two different lines. All we know
98 ;;; is that timelines with the same elasticity will grow and shrink in
99 ;;; parallel, and that proportions between two timelines of different
100 ;;; durations will be preserved.
101 (defun compute-elasticities (measures method)
102 (loop for measure in measures
103 do (loop with timelines = (timelines measure)
104 for i from 0 below (flexichain:nb-elements timelines)
105 for timeline = (flexichain:element* timelines i)
106 do (setf (elasticity timeline)
107 (max (expt (duration timeline) (spacing-style method)) 0.0001)))))
109 ;;; FIXME: there should be an :around method that adds the value
110 ;;; return by the main method to the explicit horizontal offset that
111 ;;; the user wants to impose on an element, and the existence of this
112 ;;; around method should be documented.
113 ;;; FIXME: we should probably also allow for the user to introduce
114 ;;; explicit (positive or negative) bulges that will be added in by
115 ;;; the :around method, thus allowing the user to explicitly move two
116 ;;; adjacent elements further apart, or to bring them closer together.
117 (defgeneric left-bulge (element pane)
118 (:documentation "The amount by which an element sticks out to the
119 left of the center of its timeline"))
121 ;;; FIXME: there should be an :around method that adds the value
122 ;;; return by the main method to the explicit horizontal offset that
123 ;;; the user wants to impose on an element, and the existence of this
124 ;;; around method should be documented.
125 ;;; FIXME: we should probably also allow for the user to introduce
126 ;;; explicit (positive or negative) bulges that will be added in by
127 ;;; the :around method, thus allowing the user to explicitly move two
128 ;;; adjacent elements further apart, or to bring them closer together.
129 (defgeneric right-bulge (element pane)
130 (:documentation "The amount by which an element sticks out to the
131 right of the center of its timeline"))
133 (defmethod left-bulge ((element element) pane)
134 (score-pane:staff-step 1))
136 (defmethod left-bulge ((element lyrics-element) pane)
137 (+ (score-pane:staff-step 0.5)
138 (/ (text-size pane (map 'string 'code-char (text element))) 2)))
140 (defmethod left-bulge ((element cluster) pane)
141 (+ (max (- (loop for note in (notes element)
142 when (final-accidental note)
143 minimize (final-relative-accidental-xoffset note)))
144 (if (and (non-empty-cluster-p element)
145 (eq (final-stem-direction element) :down)
146 (element-has-suspended-notes element))
147 (score-pane:staff-step 3)
148 (score-pane:staff-step 0)))
149 (score-pane:staff-step 2)))
151 (defmethod right-bulge ((element element) pane)
152 (score-pane:staff-step 1))
154 (defmethod right-bulge ((element lyrics-element) pane)
155 (+ (score-pane:staff-step 0.5)
156 (/ (text-size pane (map 'string 'code-char (text element))) 2)))
158 (defmethod right-bulge ((element cluster) pane)
159 (if (and (non-empty-cluster-p element)
160 (eq (final-stem-direction element) :up)
161 (element-has-suspended-notes element))
162 (score-pane:staff-step 5)
163 (score-pane:staff-step 2)))
165 (defmethod right-bulge ((timesig time-signature) pane)
166 ;; FIXME: this is probably wrong; it should either compute the bulge
167 ;; properly, or else approximate using (length - 0.5) *
168 ;; typical-width-of-component
169 (* (length (time-signature-components timesig))
170 (score-pane:staff-step 5)))
172 (defmethod right-bulge ((keysig key-signature) pane)
173 ;; FIXME: shares much code with DRAW-ELEMENT (KEY-SIGNATURE).
174 (let ((old-keysig (keysig keysig)))
175 (let ((bulge 0))
176 (loop with advance = 0
177 for pitch in '(6 2 5 1 4 0 3)
178 when (and (eq (aref (alterations old-keysig) pitch) :flat)
179 (not (eq (aref (alterations keysig) pitch)
180 :flat)))
181 do (incf advance (score-pane:staff-step 2))
182 finally (incf bulge (if (= advance 0) 0 (+ advance (score-pane:staff-step 2)))))
183 (loop with advance = 0
184 for pitch in '(3 0 4 1 5 2 6)
185 when (and (eq (aref (alterations old-keysig) pitch) :sharp)
186 (not (eq (aref (alterations keysig) pitch) :sharp)))
187 do (incf advance (score-pane:staff-step 2))
188 finally (incf bulge (if (= advance 0) 0 (+ advance (score-pane:staff-step 2)))))
189 (loop for pitch in '(6 2 5 1 4 0 3)
190 while (eq (aref (alterations keysig) pitch) :flat)
191 do (incf bulge (score-pane:staff-step 2)))
192 (loop for pitch in '(3 0 4 1 5 2 6)
193 while (eq (aref (alterations keysig) pitch) :sharp)
194 do (incf bulge (score-pane:staff-step 2.5)))
195 bulge)))
197 ;;; As it turns out, the spacing algorithm would be very complicated
198 ;;; if we were to take into account exactly how elements with
199 ;;; arbitrarily many timelines between them might influence the
200 ;;; overall layout. Instead we apprixmate by obtaining a closest gap
201 ;;; only between adjacent timelines as follows: first, we consider
202 ;;; adjacent elements whose timelines are also adjacent (and there is
203 ;;; a special case for the last element of a layer), and set the
204 ;;; smallest gap between the timelines to the closest possible
205 ;;; distance between the two elements...
206 (defun compute-gaps-adjacent-timelines (bars method pane)
207 (declare (ignore method))
208 (loop for bar in bars
209 do (loop for (e1 e2) on (elements bar)
210 for t1 = (timeline e1)
211 do (cond ((null e2)
212 (when (flexichain:flexi-last-p t1)
213 (setf (smallest-gap t1)
214 (max (smallest-gap t1)
215 (right-bulge e1 pane)))))
216 ((eq (flexichain:flexi-next t1)
217 (timeline e2))
218 (setf (smallest-gap t1)
219 (max (smallest-gap t1)
220 (+ (right-bulge e1 pane)
221 (left-bulge e2 pane)))))))))
223 ;;; ... Then we consider adjacent elements whose timelines are
224 ;;; separated by at least one other timeline. If the sum of the
225 ;;; distances between individual timelines is greater than or equal to
226 ;;; the closest distance between the adjacent elements (which is
227 ;;; likely if we are talking melody), then there is nothing to do,
228 ;;; since the individual distances are more restrictive than that
229 ;;; imposed by the adjacent elements. If not, we try to distribute
230 ;;; the closest distance between the two adjacent elements over the
231 ;;; individual timelines proportionally to the elasticity of the
232 ;;; timlines. If in doing so, we find that some timeline already has
233 ;;; a smallest gap that is larger than the fraction of the closest
234 ;;; distance between adjacent elements that we attribute to it, then
235 ;;; that smallest gap is subtracted from the distance we need to
236 ;;; distribute, the timeline is removed from consideration, and we
237 ;;; start over. This process must terminate (or else, the sum of the
238 ;;; closest gaps must have been larger than the distance to distribute
239 ;;; in the first place) with at least one timeline to distribute over.
240 ;;; There is a special case here, which occurs when all the
241 ;;; elasticites of the timelines to be considered is zero. In this
242 ;;; case, instead of distributing proportionally to the elasticities
243 ;;; of individual timelies, we distribute evenly between the timelines.
244 (defun compute-gaps-separated-timelines (bars method pane)
245 (declare (ignore method))
246 (flet ((handle-timelines (timelines element-gap)
247 (let ((sum-gap (reduce #'+ timelines :key #'smallest-gap))
248 (sum-elasticity (reduce #'+ timelines :key #'elasticity)))
249 (unless (> sum-gap element-gap)
250 (if (zerop sum-elasticity)
251 (loop for timeline = (find (/ element-gap (length timelines))
252 timelines
253 :key #'smallest-gap
254 :test #'<)
255 until (null timeline)
256 do (decf element-gap (smallest-gap timeline))
257 do (setf timelines (remove timeline timelines :test #'eq))
258 finally (let ((gap (/ element-gap (length timelines))))
259 (loop for timeline in timelines
260 do (setf (smallest-gap timeline) gap))))
261 (loop for timeline = (let ((gap/elasticity (/ element-gap sum-elasticity)))
262 (find-if (lambda (timeline)
263 (> (smallest-gap timeline)
264 (* (elasticity timeline) gap/elasticity)))
265 timelines))
266 until (null timeline)
267 do (decf element-gap (smallest-gap timeline))
268 do (decf sum-elasticity (elasticity timeline))
269 do (setf timelines (remove timeline timelines :test #'eq))
270 finally (let ((gap/elasticity (/ element-gap sum-elasticity)))
271 (loop for timeline in timelines
272 do (setf (smallest-gap timeline)
273 (* (elasticity timeline) gap/elasticity))))))))))
274 (loop for bar in bars
275 do (loop for (e1 e2) on (elements bar)
276 for t1 = (timeline e1)
277 do (cond ((null e2)
278 (unless (flexichain:flexi-last-p t1)
279 (let ((timelines (loop for tl = t1 then (flexichain:flexi-next tl)
280 collect tl
281 until (flexichain:flexi-last-p tl))))
282 (handle-timelines timelines (right-bulge e1 pane)))))
283 ((not (eq (flexichain:flexi-next t1)
284 (timeline e2)))
285 (let ((timelines (loop for tl = t1 then (flexichain:flexi-next tl)
286 until (eq tl (timeline e2))
287 collect tl)))
288 (handle-timelines timelines (+ (right-bulge e1 pane)
289 (left-bulge e2 pane))))))))))
291 (defun compute-gaps (measures method pane)
292 (loop for measure in measures
293 ;; initially, look only at adjacent elements whose
294 ;; corrsponding timelines are also adjacent, and at the last
295 ;; element of a bar, provided that its timeline is also the
296 ;; last one in the measure
297 do (compute-gaps-adjacent-timelines (measure-bars measure) method pane)
299 ;; then look at adjacent elements whose corresponding
300 ;; timelines are NOT adjacent, or the last element of a bar
301 ;; whose corresponding timeline is not the last one in the meaure
302 do (compute-gaps-separated-timelines (measure-bars measure) method pane)))
304 ;;; When this function is called, each timeline has an elasticity and
305 ;;; a smallest gap to the next adjacent timline (or to the end of the
306 ;;; measure). These values, together with an elasticity function at
307 ;;; the beginning of a measure, are used to compute the total
308 ;;; elasticity function of a measure.
309 (defun compute-elasticity-functions (measures method pane)
310 (loop for measure in measures
311 do (setf (prefix-elasticity-function measure)
312 (let ((prefix-width
313 (max (min-width method)
314 (if (zerop (flexichain:nb-elements (timelines measure)))
316 (loop for element in (elements (flexichain:element* (timelines measure) 0))
317 maximize (left-bulge element pane))))))
318 (make-elementary-elasticity prefix-width 0.0001)))
319 do (loop with result = (prefix-elasticity-function measure)
320 with timelines = (timelines measure)
321 for i from 0 below (flexichain:nb-elements timelines)
322 for timeline = (flexichain:element* timelines i)
323 do (setf result
324 (add-elasticities
325 result
326 (make-elementary-elasticity (smallest-gap timeline) (elasticity timeline))))
327 finally (setf (elasticity-function measure) result)))
328 (reduce #'add-elasticities measures :key #'elasticity-function))
330 (defun single-whole-rest-in-bar-p (element)
331 (let* ((bar (bar element))
332 (elements (elements bar)))
333 (and (null (cdr elements))
334 (typep element 'rest)
335 (member (notehead element) '(:long :breve :whole)))))
337 (defun compute-measure-coordinates (measure x y force)
338 (loop with timelines = (timelines measure)
339 for i from 0 below (flexichain:nb-elements timelines)
340 for timeline = (flexichain:element* timelines i)
341 and xx = (+ x (size-at-force (prefix-elasticity-function measure) force))
342 then (+ xx (max (smallest-gap timeline)
343 (* force (elasticity timeline))))
344 do (loop for element in (elements timeline)
345 do (setf (final-absolute-element-xoffset element)
346 (if (single-whole-rest-in-bar-p element)
347 (round (+ x (/ (size-at-force (elasticity-function measure) force) 2)))
348 (round (+ xx (score-pane:staff-step (xoffset element))))))))
349 (loop for bar in (measure-bars measure)
350 do (compute-bar-coordinates bar x y (size-at-force (elasticity-function measure) force))))
352 (defun draw-measure (pane measure)
353 (loop for bar in (measure-bars measure) do
354 (if (gsharp-cursor::cursors (slice bar))
355 (draw-bar pane bar)
356 (score-pane:with-light-glyphs pane (draw-bar pane bar))))
357 (let ((first-bar (car (measure-bars measure))))
358 (let ((x (final-absolute-measure-xoffset first-bar))
359 (y (system-y-position first-bar))
360 (width (final-width first-bar))
361 (staves (staves (buffer (segment (layer (slice first-bar)))))))
362 (score-pane:draw-bar-line pane (+ x width)
363 (+ y (- (score-pane:staff-step 8)))
364 (+ y (staff-yoffset (car (last staves))))))))
366 (defun compute-system-coordinates (measures x y force)
367 (loop for measure in measures
368 do (compute-measure-coordinates measure x y force)
369 do (incf x (size-at-force (elasticity-function measure) force))))
371 (defun draw-tie (pane bars n1 n2)
372 ;; FIXME: we'll want to draw ties between (nothing) and n2 eventually
373 (declare (type note n1) (type (or note null) n2))
374 (let ((x1 (+ (final-absolute-note-xoffset n1) (score-pane:staff-step 1.5)))
375 (x2 (if (typep n2 'note)
376 (- (final-absolute-note-xoffset n2) (score-pane:staff-step 1.5))
377 (+ (final-absolute-note-xoffset n1) (score-pane:staff-step 4.5))))
378 (pos (note-position n1)))
379 (if (eq (final-stem-direction (cluster n1)) :up)
380 (score-pane:with-vertical-score-position (pane (staff-yoffset (staff n1)))
381 (if (gsharp-cursor::cursors (slice (car bars)))
382 (score-pane:draw-tie-down pane x1 x2 (if (oddp pos) (1- pos) pos))
383 (score-pane:with-light-glyphs pane
384 (score-pane:draw-tie-down pane x1 x2 (if (oddp pos) (1- pos) pos)))))
385 (score-pane:with-vertical-score-position (pane (staff-yoffset (staff n1)))
386 (if (gsharp-cursor::cursors (slice (car bars)))
387 (score-pane:draw-tie-up pane x1 x2 (if (oddp pos) (1+ pos) pos))
388 (score-pane:with-light-glyphs pane
389 (score-pane:draw-tie-up pane x1 x2 (if (oddp pos) (1+ pos) pos))))))))
391 (defun notes-tieable (n1 n2)
392 (and (= (pitch n1) (pitch n2))
393 (eq (staff n1) (staff n2))
394 (eq (accidentals n1) (accidentals n2))))
396 ;;; draw the ties in BARS starting at BAR and at most LENGTH bars
397 (defun draw-ties (pane bars bar length)
398 (loop until (eq bar (car bars))
399 do (pop bars))
400 (score-pane:with-vertical-score-position
401 (pane (system-y-position (car bars)))
402 (loop with elements = (mapcan (lambda (bar) (copy-seq (elements bar)))
403 (loop for bar in bars
404 repeat length
405 collect bar))
406 for (e1 e2) on elements
407 do (when (typep e1 'cluster)
408 (loop for n1 in (notes e1)
409 do (when (tie-right n1)
410 (loop for n2 in (and (typep e2 'cluster) (notes e2))
411 do (when (and (tie-left n2)
412 (notes-tieable n1 n2))
413 (draw-tie pane bars n1 n2)
414 (return))
415 finally (draw-tie pane bars n1 nil))))))))
417 (defun draw-system (pane measures)
418 (with-new-output-record (pane)
419 (loop with length = (length measures)
420 for bar in (measure-bars (car measures))
421 do (draw-ties pane (bars (slice bar)) bar length))
422 (loop for measure in measures do
423 (draw-measure pane measure))))
425 (defun draw-staves (pane staves measures x y right-edge)
426 (loop for staff in staves do
427 (score-pane:with-vertical-score-position
428 (pane (+ y (staff-yoffset staff)))
429 (if (member staff (staves (layer (slice (bar *cursor*)))))
430 (draw-staff-and-clef pane staff measures x right-edge)
431 (score-pane:with-light-glyphs pane
432 (draw-staff-and-clef pane staff measures x right-edge))))))
434 (defun compute-and-draw-system (pane buffer staves measures method x y timesig-offset right-edge)
435 (compute-elasticities measures method)
436 (compute-gaps measures method pane)
437 (let* ((e-fun (compute-elasticity-functions measures method pane))
438 ;; FIXME: it would be much better to compress the system
439 ;; proportionally, so that every smallest gap gets shrunk
440 ;; by the same percentage
441 (force (if (> (zero-force-size e-fun) (line-width method))
443 (force-at-size e-fun (line-width method)))))
444 (compute-system-coordinates measures
445 (+ x (left-offset buffer) timesig-offset) y
446 force))
447 (draw-system pane measures)
448 (score-pane:draw-bar-line pane x
449 (+ y (- (score-pane:staff-step 8)))
450 (+ y (staff-yoffset (car (last staves)))))
451 (draw-staves pane staves measures x y right-edge))
453 (defun compute-timesig-offset (staves measures)
454 (max (* (score-pane:staff-step 2)
455 (loop for staff in staves
456 maximize
457 (if (typep staff 'fiveline-staff)
458 (count :flat (alterations (key-signature-for-staff staff measures)))
459 0)))
460 (* (score-pane:staff-step 2.5)
461 (loop for staff in staves
462 maximize
463 (if (typep staff 'fiveline-staff)
464 (count :sharp (alterations (key-signature-for-staff staff measures)))
465 0)))))
467 (defun split (sequence n method)
468 (labels ((sequence-size (start end)
469 (natural-width method
470 (reduce (lambda (seq-cost element)
471 (combine-cost method seq-cost element))
472 sequence :start start :end end
473 :initial-value nil)))
474 (split-aux (sequence start end n)
475 (if (= n 1)
476 (let ((width (sequence-size start end)))
477 (values (list (subseq sequence start end)) width width))
478 (let* ((nn (floor n 2))
479 (m (floor (* (- end start) nn) n)))
480 (multiple-value-bind (best-left minl maxl)
481 (split-aux sequence start (+ start m) nn)
482 (multiple-value-bind (best-right minr maxr)
483 (split-aux sequence (+ start m) end (- n nn))
484 (let* ((best-min (min minl minr))
485 (best-max (max maxl maxr))
486 (best-cost (/ (- best-max best-min) 2))
487 (best-splits (append best-left best-right)))
488 (cond ((and (< minl minr)
489 (< maxl maxr))
490 (loop do (incf m)
491 while (and (< minl minr)
492 (< maxl maxr)
493 (>= (- end start m) (- n nn)))
494 do (multiple-value-bind (left new-minl new-maxl)
495 (split-aux sequence start (+ start m) nn)
496 (multiple-value-bind (right new-minr new-maxr)
497 (split-aux sequence (+ start m) end (- n nn))
498 (setf minl new-minl
499 maxl new-maxl
500 minr new-minr
501 maxr new-maxr)
502 (let ((cost (/ (- (max maxl maxr) (min minl minr)) 2)))
503 (when (< cost best-cost)
504 (setf best-min (min minl minr)
505 best-max (max maxl maxr)
506 best-cost cost
507 best-splits (append left right))))))))
508 ((and (> minl minr)
509 (> maxl maxr))
510 (loop do (decf m)
511 while (and (> minl minr)
512 (> maxl maxr)
513 (>= m nn))
514 do (multiple-value-bind (left new-minl new-maxl)
515 (split-aux sequence start (+ start m) nn)
516 (multiple-value-bind (right new-minr new-maxr)
517 (split-aux sequence (+ start m) end (- n nn))
518 (setf minl new-minl
519 maxl new-maxl
520 minr new-minr
521 maxr new-maxr)
522 (let ((cost (/ (- (max maxl maxr) (min minl minr)) 2)))
523 (when (< cost best-cost)
524 (setf best-min (min minl minr)
525 best-max (max maxl maxr)
526 best-cost cost
527 best-splits (append left right)))))))))
528 (values best-splits best-min best-max))))))))
529 (split-aux sequence 0 (length sequence) n)))
531 (defun layout-page (measures n method)
532 (if (<= (length measures) n)
533 (mapcar #'list measures)
534 (split measures n method)))
536 (defmacro dopages ((measures buffer) &body body)
537 `(gsharp-measure::new-map-over-obseq-subsequences
538 (lambda (,measures) ,@body)
539 ,buffer))
541 (defun cursor-in-measures-p (cursor measures)
542 (member-if (lambda (measure) (member (bar cursor) (measure-bars measure)
543 :test #'eq))
544 measures))
546 (defun method-for-timesig (method timesig-offset)
547 (make-measure-cost-method (min-width method) (spacing-style method)
548 (- (line-width method) timesig-offset)
549 (lines-per-page method)))
551 (defun draw-page (pane buffer x y staves maxmethod page-measures)
552 (let* ((systems-per-page (gsharp-measure::systems-per-page buffer))
553 (measure-seqs (layout-page page-measures systems-per-page maxmethod)))
554 (dolist (measures measure-seqs)
555 (let* ((toffset (compute-timesig-offset staves measures))
556 (method (method-for-timesig (buffer-cost-method buffer) toffset)))
557 (compute-and-draw-system pane buffer staves measures method
558 x y toffset (right-edge buffer))
559 (incf y (+ 20 (* 70 (length staves))))))))
561 (defmethod draw-buffer (pane (buffer buffer) *cursor* x y)
562 (score-pane:with-staff-size (gsharp-buffer::rastral-size buffer)
563 (let* ((staves (staves buffer))
564 (max-timesig-offset (* (score-pane:staff-step 2.5) 7))
565 (method (method-for-timesig
566 (buffer-cost-method buffer) max-timesig-offset)))
567 (loop for staff in staves
568 for offset from 0 by 70 do
569 (setf (staff-yoffset staff) offset))
570 (dopages (page-measures buffer)
571 (when (cursor-in-measures-p *cursor* page-measures)
572 (draw-page pane buffer x y staves method page-measures))))))
574 (defmethod print-buffer (pane (buffer buffer) *cursor* x y)
575 (score-pane:with-staff-size (gsharp-buffer::rastral-size buffer)
576 (let* ((staves (staves buffer))
577 (max-timesig-offset (* (score-pane:staff-step 2.5) 7))
578 (method (method-for-timesig
579 (buffer-cost-method buffer) max-timesig-offset)))
580 (loop for staff in staves
581 for offset from 0 by 70 do
582 (setf (staff-yoffset staff) offset))
583 (let ((first t))
584 (dopages (page-measures buffer)
585 (unless first
586 (new-page pane))
587 (draw-page pane buffer x y staves method page-measures)
588 (setq first nil))))))
590 (define-stealth-mixin xelement () element
591 ((final-absolute-xoffset :accessor final-absolute-element-xoffset)))
593 (define-stealth-mixin velement () melody-element
594 (;; the position, in staff steps, of the end of the stem
595 ;; that is not attached to a note, independent of the
596 ;; staff on which it is located
597 (final-stem-position :accessor final-stem-position)
598 ;; the yoffset of the staff relative to which the end of the
599 ;; stem is located
600 (final-stem-yoffset :initform 0 :accessor final-stem-yoffset)
601 ;; the yoffset of the staff that contains the top note of
602 ;; the element
603 (top-note-staff-yoffset :accessor top-note-staff-yoffset)
604 ;; the yoffset of the staff that contains the bottom note of
605 ;; the element
606 (bot-note-staff-yoffset :accessor bot-note-staff-yoffset)))
608 (define-stealth-mixin welement () lyrics-element
611 ;;; Compute and store several important pieces of information
612 ;;; about an element:
613 ;;; * the y-offset of the staff containing the top note.
614 ;;; * the y-offset of the staff containing the bottom note.
615 (defun compute-top-bot-yoffset (element)
616 (if (and (typep element 'cluster) (notes element))
617 (let ((top-note (top-note (notes element)))
618 (bot-note (bot-note (notes element))))
619 (setf (bot-note-staff-yoffset element) (staff-yoffset (staff bot-note))
620 (top-note-staff-yoffset element) (staff-yoffset (staff top-note))))
621 (setf ;; clearly wrong. should be taken from element or layer.
622 (top-note-staff-yoffset element) 0
623 (bot-note-staff-yoffset element) 0)))
625 (defun compute-stem-length (element)
626 (let* ((top-note-pos (top-note-pos element))
627 (bot-note-pos (bot-note-pos element))
628 (top-note (reduce (lambda (n1 n2)
629 (cond ((< (staff-yoffset (staff n1))
630 (staff-yoffset (staff n2)))
632 ((> (staff-yoffset (staff n1))
633 (staff-yoffset (staff n2)))
635 ((> (note-position n1)
636 (note-position n2))
638 (t n2)))
639 (notes element)))
640 (bot-note (reduce (lambda (n1 n2)
641 (cond ((> (staff-yoffset (staff n1))
642 (staff-yoffset (staff n2)))
644 ((< (staff-yoffset (staff n1))
645 (staff-yoffset (staff n2)))
647 ((< (note-position n1)
648 (note-position n2))
650 (t n2)))
651 (notes element)))
652 (length (if (eq (final-stem-direction element) :up)
653 (cond ((<= top-note-pos -3) (- 4 top-note-pos))
654 ((<= top-note-pos 3) 7)
655 ((= top-note-pos 4) 6)
656 (t 5))
657 (cond ((>= bot-note-pos 11) (- bot-note-pos 4))
658 ((>= bot-note-pos 4) 7)
659 ((= bot-note-pos 3) 6)
660 (t 5))))
661 (nb-flags (max (rbeams element) (lbeams element))))
662 (when (> nb-flags 0)
663 (setf length (max length
664 (+ (if (eq (final-stem-direction element) :up) 6 7)
665 (* 2 (max 0 (- nb-flags 2)))))))
666 (setf (final-stem-yoffset element)
667 (staff-yoffset (staff (if (eq (final-stem-direction element) :up)
668 top-note bot-note))))
669 (setf (final-stem-position element)
670 (if (eq (final-stem-direction element) :up)
671 (+ top-note-pos length)
672 (- bot-note-pos length)))))
674 ;;; the dominating note among a bunch of notes is the
675 ;;; one that is closest to the beam, i.e. the one
676 ;;; the one that is closest to the end of the stem that
677 ;;; is not attached to a notehead.
678 (defun dominating-note (notes stem-direction)
679 (reduce (lambda (n1 n2)
680 (let ((yoff1 (staff-yoffset (staff n1)))
681 (yoff2 (staff-yoffset (staff n2))))
682 (if (eq stem-direction :up)
683 (if (< yoff1 yoff2)
685 (if (> yoff1 yoff2)
687 (if (> (pitch n1) (pitch n2)) n1 n2)))
688 (if (> yoff1 yoff2)
690 (if (< yoff1 yoff2)
692 (if (< (pitch n1) (pitch n2)) n1 n2))))))
693 notes))
695 (defun cluster-p (element)
696 (typep element 'cluster))
698 (defun map-over-cluster-pairs (fun list)
699 (loop for sublist on list
700 do (when (cluster-p (car sublist))
701 (let ((second (find-if #'cluster-p (cdr sublist))))
702 (when second
703 (funcall fun (car sublist) second))))))
705 (defun draw-beam-group (pane elements)
706 (let ((e (car elements)))
707 (when (typep e 'gsharp-buffer::staffwise-element)
708 (assert (null (cdr elements)))
709 (return-from draw-beam-group
710 (draw-element pane e (final-absolute-element-xoffset e)))))
711 (mapc #'compute-top-bot-yoffset elements)
712 (if (null (cdr elements))
713 (let ((element (car elements)))
714 (when (or (typep element 'rest) (notes element))
715 (when (non-empty-cluster-p element)
716 (compute-stem-length element))
717 (draw-element pane element)))
718 (let* ((stem-direction (final-stem-direction (car elements)))
719 (dominating-notes
720 (loop for element in elements
721 when (non-empty-cluster-p element)
722 collect (dominating-note (notes element) stem-direction)))
723 (dominating-staff
724 (staff (dominating-note dominating-notes stem-direction)))
725 (positions (mapcar (lambda (n)
726 (if (eq (staff n) dominating-staff)
727 (note-position n)
728 (if (eq stem-direction :up) -1000 1000)))
729 dominating-notes))
730 (x-positions (mapcar (lambda (element)
731 (/ (final-absolute-element-xoffset element) (score-pane:staff-step 1)))
732 elements))
733 (nb-beams (mapcar (lambda (element)
734 (max (lbeams element) (rbeams element)))
735 elements))
736 (beaming (beaming-single (mapcar #'list positions x-positions nb-beams) stem-direction))
737 (max-nb-beams (reduce #'max nb-beams))
738 (min-nb-beams (reduce #'min nb-beams)))
739 (destructuring-bind ((ss1 . offset1) (ss2 . offset2)) beaming
740 (let* ((y1 (+ ss1 (* 1/2 offset1)))
741 (y2 (+ ss2 (* 1/2 offset2)))
742 (x1 (final-absolute-element-xoffset (car elements)))
743 (x2 (final-absolute-element-xoffset (car (last elements))))
744 (slope (/ (- y2 y1) (- x2 x1))))
745 (if (eq stem-direction :up)
746 (loop for element in elements do
747 (setf (final-stem-position element)
748 (+ y1 (* slope (- (final-absolute-element-xoffset element) x1))))
749 (setf (final-stem-yoffset element)
750 (staff-yoffset dominating-staff)))
751 (loop for element in elements do
752 (setf (final-stem-position element)
753 (+ y1 (* slope (- (final-absolute-element-xoffset element) x1))))
754 (setf (final-stem-yoffset element)
755 (staff-yoffset dominating-staff)))))
756 (score-pane:with-vertical-score-position (pane (staff-yoffset dominating-staff))
757 (if (eq stem-direction :up)
758 (score-pane:with-notehead-right-offsets (right up)
759 (declare (ignore up))
760 (loop repeat min-nb-beams
761 for ss from 0 by 2
762 for offset from 0
763 do (score-pane:draw-beam pane
764 (+ (final-absolute-element-xoffset (car elements)) right) (- ss1 ss) (+ offset1 offset)
765 (+ (final-absolute-element-xoffset (car (last elements))) right) (- ss2 ss) (+ offset2 offset)))
766 (let ((region +nowhere+))
767 (loop for beams from (1+ min-nb-beams) to max-nb-beams
768 for ss from (* 2 min-nb-beams) by 2
769 for offset from min-nb-beams
770 do (map-over-cluster-pairs
771 (lambda (e1 e2)
772 (cond ((and (>= (rbeams e1) beams) (>= (lbeams e2) beams))
773 (setf region
774 (region-union region
775 (make-rectangle* (+ (final-absolute-element-xoffset e1) right) -10000
776 (+ (final-absolute-element-xoffset e2) right) 10000))))
777 ((>= (rbeams e1) beams)
778 (setf region
779 (region-union region
780 (make-rectangle* (+ (final-absolute-element-xoffset e1) right) -10000
781 (+ (final-absolute-element-xoffset e1) right (score-pane:staff-step 2)) 10000))))
782 ((>= (lbeams e2) beams)
783 (setf region
784 (region-union region
785 (make-rectangle* (+ (final-absolute-element-xoffset e2) right (score-pane:staff-step -2)) -10000
786 (+ (final-absolute-element-xoffset e2) right) 10000))))
787 (t nil)))
788 elements)
789 (with-drawing-options (pane :clipping-region region)
790 (score-pane:draw-beam pane
791 (+ (final-absolute-element-xoffset (car elements)) right) (- ss1 ss) (+ offset1 offset)
792 (+ (final-absolute-element-xoffset (car (last elements))) right) (- ss2 ss) (+ offset2 offset))))))
793 (score-pane:with-notehead-left-offsets (left down)
794 (declare (ignore down))
795 (loop repeat min-nb-beams
796 for ss from 0 by 2
797 for offset from 0
798 do (score-pane:draw-beam pane
799 (+ (final-absolute-element-xoffset (car elements)) left) (+ ss1 ss) (- offset1 offset)
800 (+ (final-absolute-element-xoffset (car (last elements))) left) (+ ss2 ss) (- offset2 offset)))
801 (let ((region +nowhere+))
802 (loop for beams from (1+ min-nb-beams) to max-nb-beams
803 for ss from (* 2 min-nb-beams) by 2
804 for offset from min-nb-beams
805 do (map-over-cluster-pairs
806 (lambda (e1 e2)
807 (cond ((and (>= (rbeams e1) beams) (>= (lbeams e2) beams))
808 (setf region
809 (region-union region
810 (make-rectangle* (+ (final-absolute-element-xoffset e1) left) -10000
811 (+ (final-absolute-element-xoffset e2) left) 10000))))
812 ((>= (rbeams e1) beams)
813 (setf region
814 (region-union region
815 (make-rectangle* (+ (final-absolute-element-xoffset e1) left) -10000
816 (+ (final-absolute-element-xoffset e1) left (score-pane:staff-step 2)) 10000))))
817 ((>= (lbeams e2) beams)
818 (setf region
819 (region-union region
820 (make-rectangle* (+ (final-absolute-element-xoffset e2) left (score-pane:staff-step -2)) -10000
821 (+ (final-absolute-element-xoffset e2) left) 10000))))
822 (t nil)))
823 elements)
824 (with-drawing-options (pane :clipping-region region)
825 (score-pane:draw-beam pane
826 (+ (final-absolute-element-xoffset (car elements)) left) (+ ss1 ss) (- offset1 offset)
827 (+ (final-absolute-element-xoffset (car (last elements))) left) (+ ss2 ss) (- offset2 offset))))))))
828 (loop for element in elements do
829 (draw-element pane element nil))))))
831 (defun draw-the-cursor (pane cursor cursor-element last-note)
832 (let* ((staff (car (staves (layer cursor))))
833 (bar (bar cursor))
834 (sy (system-y-position bar))
835 (yoffset (- (gsharp-drawing::staff-yoffset staff))))
836 (let ((region (pane-viewport-region pane)))
837 (when region
838 ;; FIXME: adjusting the viewport at this point leads to ugly
839 ;; jumps in the display when going across pages, as the page
840 ;; is first laid out and drawn, then the viewport is moved.
841 ;; If we instead cleared the pane, laid out the page, adjusted
842 ;; the viewport, and finally drew the page (and cursor) then
843 ;; that jump would probably go away.
845 ;; FIXME: this calculation only takes account of the centre of
846 ;; the cursor. Refactor this whole DRAW-THE-CURSOR function
847 ;; so that it's easy to take account of the vertical extent of
848 ;; the cursor.
849 (unless (< (bounding-rectangle-min-y region)
850 (- sy yoffset)
851 (bounding-rectangle-max-y region))
852 (let ((maxy (- (bounding-rectangle-max-y pane) (bounding-rectangle-height region))))
853 (scroll-extent pane 0 (max 0 (min maxy
854 (- sy (floor (bounding-rectangle-height region) 2)))))))))
856 (flet ((draw-cursor (x)
857 (if (typep staff 'fiveline-staff)
858 (let* ((clef (clef staff))
859 (bottom-line (bottom-line clef))
860 (lnote-offset (score-pane:staff-step (- last-note bottom-line))))
861 (draw-line* pane
862 x (+ sy (- (+ (score-pane:staff-step 12) yoffset)))
863 x (+ sy (- (+ (score-pane:staff-step -4) yoffset)))
864 :ink +yellow+)
865 (draw-line* pane
866 (- x 1) (+ sy (- (+ (score-pane:staff-step -3.4) yoffset lnote-offset)))
867 (- x 1) (+ sy (- (+ (score-pane:staff-step 3.6) yoffset lnote-offset)))
868 :ink +red+)
869 (draw-line* pane
870 (+ x 1) (+ sy (- (+ (score-pane:staff-step -3.4) yoffset lnote-offset)))
871 (+ x 1) (+ sy (- (+ (score-pane:staff-step 3.6) yoffset lnote-offset)))
872 :ink +red+))
873 (progn (draw-line* pane
874 (+ x 1) (+ sy (- (+ (score-pane:staff-step 2) yoffset)))
875 (+ x 1) (+ sy (- (+ (score-pane:staff-step -2) yoffset)))
876 :ink +red+)
877 (draw-line* pane
878 (- x 1) (+ sy (- (+ (score-pane:staff-step 2) yoffset)))
879 (- x 1) (+ sy (- (+ (score-pane:staff-step -2) yoffset)))
880 :ink +red+)))))
881 (score-pane:with-staff-size (gsharp-buffer::rastral-size (buffer cursor))
882 (let* ((x (final-absolute-measure-xoffset bar))
883 (width (final-width bar))
884 (elements (elements bar)))
885 (if (null cursor-element)
886 (draw-cursor (/ (+ (if (null elements)
888 (final-absolute-element-xoffset (car (last elements))))
889 x width) 2))
890 (loop for element in elements
891 and xx = x then (final-absolute-element-xoffset element) do
892 (when (eq element cursor-element)
893 (draw-cursor (/ (+ xx (final-absolute-element-xoffset element)) 2))))))))))
895 (defun compute-bar-coordinates (bar x y width)
896 (setf (system-y-position bar) y
897 (final-absolute-measure-xoffset bar) x
898 (final-width bar) width))
900 (defmethod draw-bar (pane (bar melody-bar))
901 (score-pane:with-vertical-score-position
902 (pane (system-y-position bar))
903 (loop for group in (beam-groups (elements bar))
904 do (draw-beam-group pane group))))
906 (defmethod draw-bar (pane (bar lyrics-bar))
907 (score-pane:with-vertical-score-position
908 (pane (system-y-position bar))
909 (let ((elements (elements bar)))
910 (loop for element in elements
911 do (draw-element pane element)))))
913 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
915 ;;; Cluster
917 (defgeneric draw-element (pane element &optional flags))
919 (defmethod draw-element :around (pane element &optional flags)
920 (call-next-method)
921 (dolist (annotation (annotations element))
922 (draw-element-annotation pane element annotation)))
924 (defgeneric draw-element-annotation (pane element annotation)
925 (:method (pane element annotation)
926 (warn "unknown annotation ~S for ~S" annotation element)))
928 ;;; FIXME: these methods work and have the right vertical behaviour;
929 ;;; the horizontal centering of the dot and the tenuto mark are all
930 ;;; wrong, sadly.
931 (defmethod draw-element-annotation
932 (pane (element cluster) (annotation (eql :staccato)))
933 (let ((direction (final-stem-direction element))
934 (x (final-absolute-element-xoffset element)))
935 (if (eq direction :up)
936 (score-pane:with-vertical-score-position (pane (bot-note-staff-yoffset element))
937 (score-pane:with-notehead-right-offsets (dx dy)
938 (score-pane:with-notehead-left-offsets (ddx ddy)
939 (let ((pos (- (bot-note-pos element) 2)))
940 (when (and (<= 0 pos) (evenp pos))
941 (setq pos (1- pos)))
942 (score-pane:draw-dot pane (+ x (/ (+ dx ddx) 2)) pos)))))
943 (score-pane:with-vertical-score-position (pane (top-note-staff-yoffset element))
944 (score-pane:with-notehead-right-offsets (dx dy)
945 (score-pane:with-notehead-left-offsets (ddx ddy)
946 (let ((pos (+ (top-note-pos element) 2)))
947 (when (and (<= pos 8) (evenp pos))
948 (setq pos (1+ pos)))
949 (score-pane:draw-dot pane (+ x (/ (+ dx ddx) 2)) pos))))))))
951 (defmethod draw-element-annotation
952 (pane (element cluster) (annotation (eql :tenuto)))
953 (let ((direction (final-stem-direction element))
954 (x (final-absolute-element-xoffset element)))
955 (if (eq direction :up)
956 (score-pane:with-vertical-score-position (pane (bot-note-staff-yoffset element))
957 (score-pane:with-notehead-right-offsets (dx dy)
958 (score-pane:with-notehead-left-offsets (ddx ddy)
959 (let ((pos (- (bot-note-pos element) 2)))
960 (when (and (<= 0 pos) (evenp pos))
961 (setq pos (1- pos)))
962 (draw-rectangle* pane (+ x ddx) (1- (score-pane:staff-step (- pos)))
963 (+ x dx) (1+ (score-pane:staff-step (- pos))))))))
964 (score-pane:with-vertical-score-position (pane (top-note-staff-yoffset element))
965 (score-pane:with-notehead-right-offsets (dx dy)
966 (score-pane:with-notehead-left-offsets (ddx ddy)
967 (let ((pos (+ (bot-note-pos element) 2)))
968 (when (and (<= pos 8) (evenp pos))
969 (setq pos (1+ pos)))
970 (draw-rectangle* pane (+ x ddx) (1- (score-pane:staff-step (- pos)))
971 (+ x dx) (1+ (score-pane:staff-step (- pos)))))))))))
973 (defmethod note-difference ((note1 note) (note2 note))
974 (- (pitch note1) (pitch note2)))
976 (defun draw-ledger-lines (pane x notes)
977 (score-pane:with-vertical-score-position (pane (staff-yoffset (staff (car notes))))
978 (let* ((positions (mapcar #'note-position notes))
979 (top-note-pos (reduce #'max positions))
980 (bot-note-pos (reduce #'min positions)))
981 (loop for pos from 10 to top-note-pos by 2
982 do (score-pane:draw-ledger-line pane x pos))
983 (loop for pos from -2 downto bot-note-pos by 2
984 do (score-pane:draw-ledger-line pane x pos)))))
986 (defun flags-drawn-p (element)
987 (let ((nb (max (rbeams element) (lbeams element))))
988 (and (> nb 0) (eq (notehead element) :filled) nb)))
990 (defun draw-flags (pane element x direction pos)
991 (let ((nb (flags-drawn-p element)))
992 (when nb
993 (if (eq direction :up)
994 (score-pane:with-notehead-right-offsets (right up)
995 (declare (ignore up))
996 (score-pane:draw-flags-down pane nb (+ x right) pos))
997 (score-pane:with-notehead-left-offsets (left down)
998 (declare (ignore down))
999 (score-pane:draw-flags-up pane nb (+ x left) pos))))))
1001 (defun draw-dots (pane nb-dots x dot-xoffset dot-pos)
1002 (when dot-pos
1003 (let ((staff-step (score-pane:staff-step 1)))
1004 (loop repeat nb-dots
1005 for xx from dot-xoffset by staff-step do
1006 (score-pane:draw-dot pane xx dot-pos)))))
1008 (defun draw-note (pane note notehead nb-dots x pos dot-xoffset dot-pos)
1009 (score-pane:with-vertical-score-position (pane (staff-yoffset (staff note)))
1010 (score-pane:draw-notehead pane notehead x pos)
1011 (when (final-accidental note)
1012 (score-pane:draw-accidental pane (final-accidental note) (final-absolute-accidental-xoffset note) pos))
1013 (draw-dots pane nb-dots x dot-xoffset dot-pos)))
1015 (defparameter *main-selected-note-colour* +blue-violet+)
1016 (defun draw-notes (pane notes dots notehead dot-xoffset)
1017 (loop for note in notes
1019 (with-drawing-options (pane :ink (if (and (gsharp::cur-notep)
1020 (eq note (gsharp::cur-note))
1021 (not (typep pane 'clim-postscript::postscript-stream)))
1022 *main-selected-note-colour* +black+))
1023 (draw-note pane note notehead dots (final-absolute-note-xoffset note) (note-position note) dot-xoffset (final-absolute-dot-ypos note)))))
1025 (defun element-has-suspended-notes (element)
1026 (not (apply #'= (mapcar #'final-relative-note-xoffset (notes element)))))
1028 ;;; draw a cluster. The stem direction and the stem position have
1029 ;;; already been computed.
1030 ;;; 1. Group notes by staff.
1031 ;;; 2. Draw the notes in each group
1032 ;;; 3. If necessary, draw ledger lines for notes in a group
1033 ;;; 4. Draw the stem, if any
1034 (defmethod draw-element (pane (element cluster) &optional (flags t))
1035 (with-new-output-record (pane)
1036 (unless (null (notes element))
1037 (let* ((direction (final-stem-direction element))
1038 (stem-pos (final-stem-position element))
1039 (stem-yoffset (final-stem-yoffset element))
1040 (groups (group-notes-by-staff (notes element)))
1041 (x (final-absolute-element-xoffset element))
1042 (dot-xoffset
1043 (let ((basic-xoffset (+ (score-pane:staff-step 2)
1044 (reduce #'max (mapcar #'final-absolute-note-xoffset (notes element))))))
1045 (if (and flags (eq direction :up) (flags-drawn-p element))
1046 (max basic-xoffset (+ (score-pane:staff-step 4) x))
1047 basic-xoffset))))
1048 (when flags
1049 (score-pane:with-vertical-score-position (pane stem-yoffset)
1050 (draw-flags pane element x direction stem-pos)))
1051 (loop for group in groups do
1052 (draw-notes pane group (dots element) (notehead element) dot-xoffset)
1053 (draw-ledger-lines pane x group))
1054 (unless (member (notehead element) '(:whole :breve))
1055 (if (eq direction :up)
1056 (score-pane:draw-right-stem
1057 pane x
1058 (- (bot-note-staff-yoffset element) (score-pane:staff-step (bot-note-pos element)))
1059 (- stem-yoffset (score-pane:staff-step stem-pos)))
1060 (score-pane:draw-left-stem
1061 pane x
1062 (- (top-note-staff-yoffset element) (score-pane:staff-step (top-note-pos element)))
1063 (- stem-yoffset (score-pane:staff-step stem-pos)))))))))
1065 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1067 ;;; Rest
1069 (defmethod draw-element (pane (element rest) &optional (flags t))
1070 (declare (ignore flags))
1071 (let ((x (final-absolute-element-xoffset element)))
1072 (score-pane:with-vertical-score-position (pane (staff-yoffset (staff element)))
1073 (score-pane:draw-rest pane (undotted-duration element) x (staff-pos element))
1074 (draw-dots pane (dots element) x (+ x (score-pane:staff-step 2)) (1+ (staff-pos element))))))
1076 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1078 ;;; Lyrics element
1080 (defmethod draw-element (pane (element lyrics-element) &optional (flags t))
1081 (declare (ignore flags))
1082 (let ((x (final-absolute-element-xoffset element)))
1083 (score-pane:with-vertical-score-position (pane (staff-yoffset (staff element)))
1084 (with-text-family (pane :serif)
1085 (draw-text* pane (map 'string 'code-char (text element))
1086 x 0 :align-x :center)))))
1088 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1090 ;;; Key signature element
1092 (defmethod draw-element (pane (keysig key-signature) &optional flags)
1093 (declare (ignore flags))
1094 (let ((staff (staff keysig))
1095 (old-keysig (keysig keysig))
1096 (x (final-absolute-element-xoffset keysig)))
1097 (score-pane:with-vertical-score-position (pane (staff-yoffset staff))
1098 (let ((yoffset (b-position (clef staff))))
1099 (loop with advance = 0
1100 for pitch in '(6 2 5 1 4 0 3)
1101 for line in '(0 3 -1 2 -2 1 -3)
1102 when (and (eq (aref (alterations old-keysig) pitch) :flat)
1103 (not (eq (aref (alterations keysig) pitch)
1104 :flat)))
1105 do (score-pane:draw-accidental
1106 pane :natural (+ x advance) (+ line yoffset))
1107 and do (incf advance (score-pane:staff-step 2))
1108 finally (incf x (if (= advance 0) 0 (+ advance (score-pane:staff-step 2))))))
1109 (let ((yoffset (f-position (clef staff))))
1110 (loop with advance = 0
1111 for pitch in '(3 0 4 1 5 2 6)
1112 for line in '(0 -3 1 -2 -5 -1 -4)
1113 when (and (eq (aref (alterations old-keysig) pitch) :sharp)
1114 (not (eq (aref (alterations keysig) pitch) :sharp)))
1115 do (score-pane:draw-accidental pane :natural (+ x advance) (+ line yoffset))
1116 and do (incf advance (score-pane:staff-step 2))
1117 finally (incf x (if (= advance 0) 0 (+ advance (score-pane:staff-step 2))))))
1119 (let ((yoffset (b-position (clef staff))))
1120 (loop for pitch in '(6 2 5 1 4 0 3)
1121 for line in '(0 3 -1 2 -2 1 -3)
1122 for x from x by (score-pane:staff-step 2)
1123 while (eq (aref (alterations keysig) pitch) :flat)
1124 do (score-pane:draw-accidental pane :flat x (+ line yoffset))))
1125 (let ((yoffset (f-position (clef staff))))
1126 (loop for pitch in '(3 0 4 1 5 2 6)
1127 for line in '(0 -3 1 -2 -5 -1 -4)
1128 for x from x by (score-pane:staff-step 2.5)
1129 while (eq (aref (alterations keysig) pitch) :sharp)
1130 do (score-pane:draw-accidental pane :sharp x (+ line yoffset)))))))
1132 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1134 ;;; Time signature element
1136 (defmethod draw-element (pane (timesig time-signature) &optional (flags t))
1137 (declare (ignore flags))
1138 (let ((staff (staff timesig))
1139 (x (final-absolute-element-xoffset timesig)))
1140 (score-pane:with-vertical-score-position (pane (staff-yoffset staff))
1141 (dolist (component (time-signature-components timesig))
1142 (score-pane:draw-time-signature-component pane component x)))))