2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2002--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "accidental-placement.hh"
22 #include "accidental-interface.hh"
25 #include "note-collision.hh"
26 #include "note-column.hh"
27 #include "pointer-group-interface.hh"
28 #include "rhythmic-head.hh"
30 #include "skyline-pair.hh"
31 #include "stream-event.hh"
35 accidental_pitch (Grob *acc)
37 SCM cause = acc->get_parent (Y_AXIS)->get_property ("cause");
39 Stream_event *mcause = unsmob<Stream_event> (cause);
42 programming_error ("note head has no event cause");
46 return unsmob<Pitch> (mcause->get_property ("pitch"));
50 Accidental_placement::add_accidental (Grob *me, Grob *a, bool stagger, long context_hash)
52 Pitch *p = accidental_pitch (a);
56 a->set_parent (me, X_AXIS);
57 long n = p->get_notename ();
59 SCM accs = me->get_object ("accidental-grobs");
60 SCM key = scm_cons (scm_from_int (n), scm_from_long (stagger ? context_hash : 1));
61 // assoc because we're dealing with pairs
62 SCM entry = scm_assoc (key, accs);
63 if (scm_is_false (entry))
66 entry = scm_cdr (entry);
68 entry = scm_cons (a->self_scm (), entry);
70 accs = scm_assoc_set_x (accs, key, entry);
72 me->set_object ("accidental-grobs", accs);
76 Split into break reminders.
79 Accidental_placement::split_accidentals (Grob *accs,
80 vector<Grob *> *break_reminder,
81 vector<Grob *> *real_acc)
83 for (SCM acs = accs->get_object ("accidental-grobs"); scm_is_pair (acs);
85 for (SCM s = scm_cdar (acs); scm_is_pair (s); s = scm_cdr (s))
87 Grob *a = unsmob<Grob> (scm_car (s));
89 if (unsmob<Grob> (a->get_object ("tie"))
90 && !to_boolean (a->get_property ("forced")))
91 break_reminder->push_back (a);
93 real_acc->push_back (a);
98 Accidental_placement::get_relevant_accidentals (vector<Grob *> const &elts, Grob *left)
103 bool right = dynamic_cast<Item *> (left)->break_status_dir () == RIGHT;
105 for (vsize i = 0; i < elts.size (); i++)
107 split_accidentals (elts[i], &br, &ra);
109 ret.insert (ret.end (), ra.begin (), ra.end ());
112 ret.insert (ret.end (), br.begin (), br.end ());
117 struct Accidental_placement_entry
119 Skyline_pair horizontal_skylines_;
120 vector<Grob *> grobs_;
123 Real ape_priority (Accidental_placement_entry const *a)
125 // right is up because we're horizontal
126 return a->horizontal_skylines_.right ();
129 bool ape_less (Accidental_placement_entry *const &a,
130 Accidental_placement_entry *const &b)
132 vsize size_a = a->grobs_.size ();
133 vsize size_b = b->grobs_.size ();
134 if (size_a != size_b)
135 return size_b < size_a;
137 return ape_priority (a) < ape_priority (b);
141 This function provides a method for sorting accidentals that belong to the
142 same note. The accidentals that this function considers to be "smallest"
143 will be placed to the left of the "larger" accidentals.
145 Naturals are the largest (so that they don't get confused with cancellation
146 naturals); apart from that, we order according to the alteration (so
147 double-flats are the smallest).
149 Precondition: the accidentals are attached to NoteHeads of the same note
150 name -- the octaves, however, may be different.
153 acc_less (Grob *const &a, Grob *const &b)
155 Pitch *p = accidental_pitch (a);
156 Pitch *q = accidental_pitch (b);
160 programming_error ("these accidentals do not have a pitch");
164 if (p->get_octave () != q->get_octave ())
165 return p->get_octave () < q->get_octave ();
167 if (p->get_alteration () == Rational (0))
169 if (q->get_alteration () == Rational (0))
172 return p->get_alteration () < q->get_alteration ();
184 stagger_apes (vector<Accidental_placement_entry *> *apes)
186 vector<Accidental_placement_entry *> asc = *apes;
188 vector_sort (asc, &ape_less);
189 // we do the staggering below based on size
190 // this ensures that if a placement has 4 entries, it will
191 // always be closer to the NoteColumn than a placement with 1
192 // this allows accidentals to be on-average closer to notes
193 // while still preserving octave alignment
194 vector<vector<Accidental_placement_entry *> > ascs;
197 for (vsize i = 0; i < asc.size (); i++)
199 vsize my_sz = asc[i]->grobs_.size ();
201 ascs.push_back (vector<Accidental_placement_entry *> ());
202 ascs.back ().push_back (asc[i]);
208 for (vsize i = 0; i < ascs.size (); i++)
211 for (vsize j = 0; j < ascs[i].size ();)
213 Accidental_placement_entry *a = 0;
230 static vector<Accidental_placement_entry *>
231 build_apes (SCM accs)
233 vector<Accidental_placement_entry *> apes;
234 for (SCM s = accs; scm_is_pair (s); s = scm_cdr (s))
236 Accidental_placement_entry *ape = new Accidental_placement_entry;
238 for (SCM t = scm_cdar (s); scm_is_pair (t); t = scm_cdr (t))
239 ape->grobs_.push_back (unsmob<Grob> (scm_car (t)));
241 apes.push_back (ape);
248 set_ape_skylines (Accidental_placement_entry *ape,
249 Grob **common, Real padding)
251 vector<Grob *> accs (ape->grobs_);
252 vector_sort (accs, &acc_less);
254 /* We know that each accidental has the same note name and we assume that
255 accidentals in different octaves won't collide. If two or more
256 accidentals are in the same octave:
257 1) if they are the same accidental, print them in overstrike
258 2) otherwise, shift one to the left so they don't overlap. */
261 Real last_offset = 0;
262 Rational last_alteration (0);
263 for (vsize i = accs.size (); i--;)
266 Pitch *p = accidental_pitch (a);
271 if (i == accs.size () - 1 || p->get_octave () != last_octave)
274 offset = a->extent (a, X_AXIS)[LEFT] - padding;
276 else if (p->get_alteration () == last_alteration)
277 a->translate_axis (last_offset, X_AXIS);
278 else /* Our alteration is different from the last one */
280 Real this_offset = offset - a->extent (a, X_AXIS)[RIGHT];
281 a->translate_axis (this_offset, X_AXIS);
283 last_offset = this_offset;
284 offset -= a->extent (a, X_AXIS).length () + padding;
287 if (Skyline_pair *sky = unsmob<Skyline_pair> (a->get_property ("horizontal-skylines")))
289 Skyline_pair copy (*sky);
290 copy.raise (a->relative_coordinate (common[X_AXIS], X_AXIS));
291 copy.shift (a->relative_coordinate (common[Y_AXIS], Y_AXIS));
292 ape->horizontal_skylines_.merge (copy);
295 last_octave = p->get_octave ();
296 last_alteration = p->get_alteration ();
300 static vector<Grob *>
301 extract_heads_and_stems (vector<Accidental_placement_entry *> const &apes)
303 vector<Grob *> note_cols;
306 for (vsize i = apes.size (); i--;)
308 Accidental_placement_entry *ape = apes[i];
309 for (vsize j = ape->grobs_.size (); j--;)
311 Grob *acc = ape->grobs_[j];
312 Grob *head = acc->get_parent (Y_AXIS);
313 Grob *col = head->get_parent (X_AXIS);
315 if (has_interface<Note_column> (col))
316 note_cols.push_back (col);
318 ret.push_back (head);
323 This is a little kludgy: in case there are note columns without
324 accidentals, we get them from the Note_collision objects.
326 for (vsize i = note_cols.size (); i--;)
328 Grob *c = note_cols[i]->get_parent (X_AXIS);
329 if (has_interface<Note_collision_interface> (c))
331 extract_grob_set (c, "elements", columns);
332 concat (note_cols, columns);
336 /* Now that we have all of the columns, grab all of the note-heads */
337 for (vsize i = note_cols.size (); i--;)
338 concat (ret, extract_grob_array (note_cols[i], "note-heads"));
340 /* Now that we have all of the heads, grab all of the stems */
341 for (vsize i = ret.size (); i--;)
342 if (Grob *s = Rhythmic_head::get_stem (ret[i]))
350 common_refpoint_of_accidentals (vector<Accidental_placement_entry *> const &apes, Axis a)
354 for (vsize i = apes.size (); i--;)
355 for (vsize j = apes[i]->grobs_.size (); j--;)
358 ret = apes[i]->grobs_[j];
360 ret = ret->common_refpoint (apes[i]->grobs_[j], a);
367 build_heads_skyline (vector<Grob *> const &heads_and_stems,
370 vector<Box> head_extents;
371 for (vsize i = heads_and_stems.size (); i--;)
372 head_extents.push_back (Box (heads_and_stems[i]->extent (common[X_AXIS], X_AXIS),
373 heads_and_stems[i]->pure_y_extent (common[Y_AXIS], 0, INT_MAX)));
375 return Skyline (head_extents, Y_AXIS, LEFT);
379 Position the apes, starting from the right, so that they don't collide.
380 Return the total width.
383 position_apes (Grob *me,
384 vector<Accidental_placement_entry *> const &apes,
385 Skyline const &heads_skyline)
387 Real padding = robust_scm2double (me->get_property ("padding"), 0.2);
388 Skyline left_skyline = heads_skyline;
389 left_skyline.raise (-robust_scm2double (me->get_property ("right-padding"), 0));
392 Add accs entries right-to-left.
395 Real last_offset = 0.0;
396 for (vsize i = apes.size (); i-- > 0;)
398 Accidental_placement_entry *ape = apes[i];
400 Real offset = -ape->horizontal_skylines_[RIGHT]
401 .distance (left_skyline, 0.1);
403 offset = last_offset;
407 Skyline new_left_skyline = ape->horizontal_skylines_[LEFT];
408 new_left_skyline.raise (offset);
409 new_left_skyline.merge (left_skyline);
410 left_skyline = new_left_skyline;
412 /* Shift all of the accidentals in this ape */
413 for (vsize j = ape->grobs_.size (); j--;)
414 ape->grobs_[j]->translate_axis (offset, X_AXIS);
416 for (LEFT_and_RIGHT (d))
418 Real mh = ape->horizontal_skylines_[d].max_height ();
420 width.add_point (mh + offset);
423 last_offset = offset;
430 This routine computes placements of accidentals. During
431 add_accidental (), accidentals are already grouped by note, so that
432 octaves are placed above each other; they form columns. Then the
433 columns are sorted: the biggest columns go closest to the note.
434 Then the columns are spaced as closely as possible (using skyline
438 TODO: more advanced placement. Typically, the accs should be placed
439 to form a C shape, like this
447 The naturals should be left of the C as well; they should
450 Note that this placement problem looks NP hard, so we just use a
451 simple strategy, not an optimal choice.
455 TODO: there should be more space in the following situation
468 MAKE_SCHEME_CALLBACK (Accidental_placement, calc_positioning_done, 1);
470 Accidental_placement::calc_positioning_done (SCM smob)
472 Grob *me = unsmob<Grob> (smob);
476 me->set_property ("positioning-done", SCM_BOOL_T);
478 SCM accs = me->get_object ("accidental-grobs");
479 if (!scm_is_pair (accs))
482 vector<Accidental_placement_entry *> apes = build_apes (accs);
484 Grob *common[] = {me, 0};
486 vector<Grob *> heads_and_stems = extract_heads_and_stems (apes);
488 common[Y_AXIS] = common_refpoint_of_accidentals (apes, Y_AXIS);
489 common[Y_AXIS] = common_refpoint_of_array (heads_and_stems, common[Y_AXIS], Y_AXIS);
490 common[X_AXIS] = common_refpoint_of_array (heads_and_stems, me, X_AXIS);
491 Real padding = robust_scm2double (me->get_property ("padding"), 0.2);
493 for (vsize i = apes.size (); i--;)
494 set_ape_skylines (apes[i], common, padding);
495 Skyline heads_skyline = build_heads_skyline (heads_and_stems, common);
497 stagger_apes (&apes);
498 Interval width = position_apes (me, apes, heads_skyline);
500 me->flush_extent_cache (X_AXIS);
501 me->set_property ("X-extent", ly_interval2scm (width));
503 junk_pointers (apes);
508 ADD_INTERFACE (Accidental_placement,
509 "Resolve accidental collisions.",