2 accidental-placement.cc -- implement Accidental_placement
4 source file of the GNU LilyPond music typesetter
6 (c) 2002--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
10 #include "accidental-placement.hh"
13 #include "rhythmic-head.hh"
14 #include "accidental-interface.hh"
16 #include "note-collision.hh"
17 #include "note-column.hh"
18 #include "pointer-group-interface.hh"
20 #include "stream-event.hh"
24 accidental_pitch (Grob
*acc
)
26 SCM cause
= acc
->get_parent (Y_AXIS
)->get_property ("cause");
28 Stream_event
*mcause
= unsmob_stream_event (cause
);
31 programming_error ("note head has no event cause");
35 return unsmob_pitch (mcause
->get_property ("pitch"));
39 Accidental_placement::add_accidental (Grob
*me
, Grob
*a
)
41 Pitch
*p
= accidental_pitch (a
);
45 a
->set_parent (me
, X_AXIS
);
46 a
->set_property ("X-offset", Grob::x_parent_positioning_proc
);
47 int n
= p
->get_notename ();
49 SCM accs
= me
->get_object ("accidental-grobs");
50 SCM key
= scm_from_int (n
);
51 SCM entry
= scm_assq (key
, accs
);
52 if (entry
== SCM_BOOL_F
)
55 entry
= scm_cdr (entry
);
57 entry
= scm_cons (a
->self_scm (), entry
);
59 accs
= scm_assq_set_x (accs
, key
, entry
);
61 me
->set_object ("accidental-grobs", accs
);
65 Split into break reminders.
68 Accidental_placement::split_accidentals (Grob
*accs
,
69 vector
<Grob
*> *break_reminder
,
70 vector
<Grob
*> *real_acc
)
72 for (SCM acs
= accs
->get_object ("accidental-grobs"); scm_is_pair (acs
);
74 for (SCM s
= scm_cdar (acs
); scm_is_pair (s
); s
= scm_cdr (s
))
76 Grob
*a
= unsmob_grob (scm_car (s
));
78 if (unsmob_grob (a
->get_object ("tie"))
79 && !to_boolean (a
->get_property ("forced")))
80 break_reminder
->push_back (a
);
82 real_acc
->push_back (a
);
87 Accidental_placement::get_relevant_accidentals (vector
<Grob
*> const &elts
, Grob
*left
)
92 bool right
= dynamic_cast<Item
*> (left
)->break_status_dir () == RIGHT
;
94 for (vsize i
= 0; i
< elts
.size (); i
++)
96 split_accidentals (elts
[i
], &br
, &ra
);
98 ret
.insert (ret
.end (), ra
.begin (), ra
.end ());
101 ret
.insert (ret
.end (), br
.begin (), br
.end ());
106 struct Accidental_placement_entry
108 Skyline left_skyline_
;
109 Skyline right_skyline_
;
110 Interval vertical_extent_
;
111 vector
<Box
> extents_
;
112 vector
<Grob
*> grobs_
;
115 Real
ape_priority (Accidental_placement_entry
const *a
)
117 return a
->vertical_extent_
[UP
];
120 bool ape_less (Accidental_placement_entry
*const &a
,
121 Accidental_placement_entry
*const &b
)
123 return ape_priority (a
) < ape_priority (b
);
127 This function provides a method for sorting accidentals that belong to the
128 same note. The accidentals that this function considers to be "smallest"
129 will be placed to the left of the "larger" accidentals.
131 Naturals are the largest (so that they don't get confused with cancellation
132 naturals); apart from that, we order according to the alteration (so
133 double-flats are the smallest).
135 Precondition: the accidentals are attached to NoteHeads of the same note
136 name -- the octaves, however, may be different.
139 acc_less (Grob
*const &a
, Grob
*const &b
)
141 Pitch
*p
= accidental_pitch (a
);
142 Pitch
*q
= accidental_pitch (b
);
146 programming_error ("these accidentals do not have a pitch");
150 if (p
->get_octave () != q
->get_octave ())
151 return p
->get_octave () < q
->get_octave ();
153 if (p
->get_alteration () == Rational (0))
155 if (q
->get_alteration () == Rational (0))
158 return p
->get_alteration () < q
->get_alteration ();
170 stagger_apes (vector
<Accidental_placement_entry
*> *apes
)
172 vector
<Accidental_placement_entry
*> asc
= *apes
;
174 vector_sort (asc
, &ape_less
);
179 for (vsize i
= 0; i
< asc
.size ();)
181 Accidental_placement_entry
*a
= 0;
197 static vector
<Accidental_placement_entry
*>
198 build_apes (SCM accs
)
200 vector
<Accidental_placement_entry
*> apes
;
201 for (SCM s
= accs
; scm_is_pair (s
); s
= scm_cdr (s
))
203 Accidental_placement_entry
*ape
= new Accidental_placement_entry
;
205 for (SCM t
= scm_cdar (s
); scm_is_pair (t
); t
= scm_cdr (t
))
206 ape
->grobs_
.push_back (unsmob_grob (scm_car (t
)));
208 apes
.push_back (ape
);
215 set_ape_skylines (Accidental_placement_entry
*ape
,
218 vector
<Grob
*> accs (ape
->grobs_
);
219 vector_sort (accs
, &acc_less
);
221 /* We know that each accidental has the same note name and we assume that
222 accidentals in different octaves won't collide. If two or more
223 accidentals are in the same octave:
224 1) if they are the same accidental, print them in overstrike
225 2) otherwise, shift one to the left so they don't overlap. */
228 Real last_offset
= 0;
229 Rational
last_alteration (0);
230 for (vsize i
= accs
.size (); i
--;)
233 Pitch
*p
= accidental_pitch (a
);
238 if (i
== accs
.size () - 1 || p
->get_octave () != last_octave
)
241 offset
= a
->extent (a
, X_AXIS
)[LEFT
] - 0.2;
243 else if (p
->get_alteration () == last_alteration
)
244 a
->translate_axis (last_offset
, X_AXIS
);
245 else /* Our alteration is different from the last one */
247 Real this_offset
= offset
- a
->extent (a
, X_AXIS
)[RIGHT
];
248 a
->translate_axis (this_offset
, X_AXIS
);
250 /* FIXME: read the padding from the AccidentalPlacement grob */
251 last_offset
= this_offset
;
252 offset
-= a
->extent (a
, X_AXIS
).length () + 0.2;
255 vector
<Box
> boxes
= Accidental_interface::accurate_boxes (a
, common
);
256 ape
->extents_
.insert (ape
->extents_
.end (), boxes
.begin (), boxes
.end ());
258 for (vsize j
= boxes
.size (); j
--;)
259 ape
->vertical_extent_
.unite (boxes
[j
][Y_AXIS
]);
261 last_octave
= p
->get_octave ();
262 last_alteration
= p
->get_alteration ();
264 ape
->left_skyline_
= Skyline (ape
->extents_
, 0, Y_AXIS
, LEFT
);
265 ape
->right_skyline_
= Skyline (ape
->extents_
, 0, Y_AXIS
, RIGHT
);
269 extract_heads_and_stems (vector
<Accidental_placement_entry
*> const &apes
)
271 vector
<Grob
*> note_cols
;
274 for (vsize i
= apes
.size (); i
--;)
276 Accidental_placement_entry
*ape
= apes
[i
];
277 for (vsize j
= ape
->grobs_
.size (); j
--;)
279 Grob
*acc
= ape
->grobs_
[j
];
280 Grob
*head
= acc
->get_parent (Y_AXIS
);
281 Grob
*col
= head
->get_parent (X_AXIS
);
283 if (Note_column::has_interface (col
))
284 note_cols
.push_back (col
);
286 ret
.push_back (head
);
291 This is a little kludgy: in case there are note columns without
292 accidentals, we get them from the Note_collision objects.
294 for (vsize i
= note_cols
.size (); i
--;)
296 Grob
*c
= note_cols
[i
]->get_parent (X_AXIS
);
297 if (Note_collision_interface::has_interface (c
))
299 extract_grob_set (c
, "elements", columns
);
300 concat (note_cols
, columns
);
304 /* Now that we have all of the columns, grab all of the note-heads */
305 for (vsize i
= note_cols
.size (); i
--;)
306 concat (ret
, extract_grob_array (note_cols
[i
], "note-heads"));
308 /* Now that we have all of the heads, grab all of the stems */
309 for (vsize i
= ret
.size (); i
--;)
310 if (Grob
*s
= Rhythmic_head::get_stem (ret
[i
]))
314 vector_sort (ret
, less
<Grob
*> ());
320 common_refpoint_of_accidentals (vector
<Accidental_placement_entry
*> const &apes
, Axis a
)
324 for (vsize i
= apes
.size (); i
--;)
325 for (vsize j
= apes
[i
]->grobs_
.size (); j
--;)
328 ret
= apes
[i
]->grobs_
[j
];
330 ret
= ret
->common_refpoint (apes
[i
]->grobs_
[j
], a
);
337 build_heads_skyline (vector
<Grob
*> const &heads_and_stems
,
340 vector
<Box
> head_extents
;
341 for (vsize i
= heads_and_stems
.size (); i
--;)
342 head_extents
.push_back (Box (heads_and_stems
[i
]->extent (common
[X_AXIS
], X_AXIS
),
343 heads_and_stems
[i
]->pure_height (common
[Y_AXIS
], 0, INT_MAX
)));
345 return Skyline (head_extents
, 0, Y_AXIS
, LEFT
);
349 Position the apes, starting from the right, so that they don't collide.
350 Return the total width.
353 position_apes (Grob
*me
,
354 vector
<Accidental_placement_entry
*> const &apes
,
355 Skyline
const &heads_skyline
)
357 Real padding
= robust_scm2double (me
->get_property ("padding"), 0.2);
358 Skyline left_skyline
= heads_skyline
;
359 left_skyline
.raise (-robust_scm2double (me
->get_property ("right-padding"), 0));
362 Add accs entries right-to-left.
365 Real last_offset
= 0.0;
366 for (vsize i
= apes
.size (); i
-- > 0;)
368 Accidental_placement_entry
*ape
= apes
[i
];
370 Real offset
= -ape
->right_skyline_
.distance (left_skyline
);
372 offset
= last_offset
;
376 Skyline new_left_skyline
= ape
->left_skyline_
;
377 new_left_skyline
.raise (offset
);
378 new_left_skyline
.merge (left_skyline
);
379 left_skyline
= new_left_skyline
;
381 /* Shift all of the accidentals in this ape */
382 for (vsize j
= ape
->grobs_
.size (); j
--;)
383 ape
->grobs_
[j
]->translate_axis (offset
, X_AXIS
);
385 for (vsize j
= ape
->extents_
.size (); j
--;)
386 width
.unite (offset
+ ape
->extents_
[j
][X_AXIS
]);
388 last_offset
= offset
;
396 This routine computes placements of accidentals. During
397 add_accidental (), accidentals are already grouped by note, so that
398 octaves are placed above each other; they form columns. Then the
399 columns are sorted: the biggest columns go closest to the note.
400 Then the columns are spaced as closely as possible (using skyline
404 TODO: more advanced placement. Typically, the accs should be placed
405 to form a C shape, like this
414 The naturals should be left of the C as well; they should
417 Note that this placement problem looks NP hard, so we just use a
418 simple strategy, not an optimal choice.
422 TODO: there should be more space in the following situation
436 MAKE_SCHEME_CALLBACK (Accidental_placement
, calc_positioning_done
, 1);
438 Accidental_placement::calc_positioning_done (SCM smob
)
440 Grob
*me
= unsmob_grob (smob
);
444 me
->set_property ("positioning-done", SCM_BOOL_T
);
446 SCM accs
= me
->get_object ("accidental-grobs");
447 if (!scm_is_pair (accs
))
450 vector
<Accidental_placement_entry
*> apes
= build_apes (accs
);
452 Grob
*common
[] = {me
, 0};
454 vector
<Grob
*> heads_and_stems
= extract_heads_and_stems (apes
);
456 common
[Y_AXIS
] = common_refpoint_of_accidentals (apes
, Y_AXIS
);
457 common
[Y_AXIS
] = common_refpoint_of_array (heads_and_stems
, common
[Y_AXIS
], Y_AXIS
);
458 common
[X_AXIS
] = common_refpoint_of_array (heads_and_stems
, me
, X_AXIS
);
460 for (vsize i
= apes
.size (); i
--;)
461 set_ape_skylines (apes
[i
], common
);
462 Skyline heads_skyline
= build_heads_skyline (heads_and_stems
, common
);
464 stagger_apes (&apes
);
465 Interval width
= position_apes (me
, apes
, heads_skyline
);
467 me
->flush_extent_cache (X_AXIS
);
468 me
->set_property ("X-extent", ly_interval2scm (width
));
470 junk_pointers (apes
);
475 ADD_INTERFACE (Accidental_placement
,
476 "Resolve accidental collisions.",