Fix type predicates/docstrings for two music properties.
[lilypond/mpolesky.git] / lily / accidental-placement.cc
blob8e9c854415111cb975677ac391840de826a46f03
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2002--2010 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/>.
21 #include "accidental-placement.hh"
23 #include "item.hh"
24 #include "rhythmic-head.hh"
25 #include "accidental-interface.hh"
26 #include "music.hh"
27 #include "note-collision.hh"
28 #include "note-column.hh"
29 #include "pointer-group-interface.hh"
30 #include "skyline.hh"
31 #include "stream-event.hh"
32 #include "warn.hh"
34 static Pitch*
35 accidental_pitch (Grob *acc)
37 SCM cause = acc->get_parent (Y_AXIS)->get_property ("cause");
39 Stream_event *mcause = unsmob_stream_event (cause);
40 if (!mcause)
42 programming_error ("note head has no event cause");
43 return 0;
46 return unsmob_pitch (mcause->get_property ("pitch"));
49 void
50 Accidental_placement::add_accidental (Grob *me, Grob *a)
52 Pitch *p = accidental_pitch (a);
53 if (!p)
54 return;
56 a->set_parent (me, X_AXIS);
57 a->set_property ("X-offset", Grob::x_parent_positioning_proc);
58 int n = p->get_notename ();
60 SCM accs = me->get_object ("accidental-grobs");
61 SCM key = scm_from_int (n);
62 SCM entry = scm_assq (key, accs);
63 if (entry == SCM_BOOL_F)
64 entry = SCM_EOL;
65 else
66 entry = scm_cdr (entry);
68 entry = scm_cons (a->self_scm (), entry);
70 accs = scm_assq_set_x (accs, key, entry);
72 me->set_object ("accidental-grobs", accs);
76 Split into break reminders.
78 void
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);
84 acs = scm_cdr (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);
92 else
93 real_acc->push_back (a);
97 vector<Grob*>
98 Accidental_placement::get_relevant_accidentals (vector<Grob*> const &elts, Grob *left)
100 vector<Grob*> br;
101 vector<Grob*> ra;
102 vector<Grob*> ret;
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 ());
111 if (right)
112 ret.insert (ret.end (), br.begin (), br.end ());
114 return ret;
117 struct Accidental_placement_entry
119 Skyline left_skyline_;
120 Skyline right_skyline_;
121 Interval vertical_extent_;
122 vector<Box> extents_;
123 vector<Grob*> grobs_;
126 Real ape_priority (Accidental_placement_entry const *a)
128 return a->vertical_extent_[UP];
131 bool ape_less (Accidental_placement_entry *const &a,
132 Accidental_placement_entry *const &b)
134 return ape_priority (a) < ape_priority (b);
138 This function provides a method for sorting accidentals that belong to the
139 same note. The accidentals that this function considers to be "smallest"
140 will be placed to the left of the "larger" accidentals.
142 Naturals are the largest (so that they don't get confused with cancellation
143 naturals); apart from that, we order according to the alteration (so
144 double-flats are the smallest).
146 Precondition: the accidentals are attached to NoteHeads of the same note
147 name -- the octaves, however, may be different.
149 static bool
150 acc_less (Grob *const &a, Grob *const &b)
152 Pitch *p = accidental_pitch (a);
153 Pitch *q = accidental_pitch (b);
155 if (!p || !q)
157 programming_error ("these accidentals do not have a pitch");
158 return false;
161 if (p->get_octave () != q->get_octave ())
162 return p->get_octave () < q->get_octave ();
164 if (p->get_alteration () == Rational (0))
165 return false;
166 if (q->get_alteration () == Rational (0))
167 return true;
169 return p->get_alteration () < q->get_alteration ();
173 TODO: should favor
178 placement
180 void
181 stagger_apes (vector<Accidental_placement_entry*> *apes)
183 vector<Accidental_placement_entry*> asc = *apes;
185 vector_sort (asc, &ape_less);
187 apes->clear ();
189 int parity = 1;
190 for (vsize i = 0; i < asc.size ();)
192 Accidental_placement_entry *a = 0;
193 if (parity)
195 a = asc.back ();
196 asc.pop_back ();
198 else
199 a = asc[i++];
201 apes->push_back (a);
202 parity = !parity;
205 reverse (*apes);
208 static vector<Accidental_placement_entry*>
209 build_apes (SCM accs)
211 vector<Accidental_placement_entry*> apes;
212 for (SCM s = accs; scm_is_pair (s); s = scm_cdr (s))
214 Accidental_placement_entry *ape = new Accidental_placement_entry;
216 for (SCM t = scm_cdar (s); scm_is_pair (t); t = scm_cdr (t))
217 ape->grobs_.push_back (unsmob_grob (scm_car (t)));
219 apes.push_back (ape);
222 return apes;
225 static void
226 set_ape_skylines (Accidental_placement_entry *ape,
227 Grob **common)
229 vector<Grob*> accs (ape->grobs_);
230 vector_sort (accs, &acc_less);
232 /* We know that each accidental has the same note name and we assume that
233 accidentals in different octaves won't collide. If two or more
234 accidentals are in the same octave:
235 1) if they are the same accidental, print them in overstrike
236 2) otherwise, shift one to the left so they don't overlap. */
237 int last_octave = 0;
238 Real offset = 0;
239 Real last_offset = 0;
240 Rational last_alteration (0);
241 for (vsize i = accs.size (); i--;)
243 Grob *a = accs[i];
244 Pitch *p = accidental_pitch (a);
246 if (!p)
247 continue;
249 if (i == accs.size () - 1 || p->get_octave () != last_octave)
251 last_offset = 0;
252 offset = a->extent (a, X_AXIS)[LEFT] - 0.2;
254 else if (p->get_alteration () == last_alteration)
255 a->translate_axis (last_offset, X_AXIS);
256 else /* Our alteration is different from the last one */
258 Real this_offset = offset - a->extent (a, X_AXIS)[RIGHT];
259 a->translate_axis (this_offset, X_AXIS);
261 /* FIXME: read the padding from the AccidentalPlacement grob */
262 last_offset = this_offset;
263 offset -= a->extent (a, X_AXIS).length () + 0.2;
266 vector<Box> boxes = Accidental_interface::accurate_boxes (a, common);
267 ape->extents_.insert (ape->extents_.end (), boxes.begin (), boxes.end ());
269 for (vsize j = boxes.size (); j--;)
270 ape->vertical_extent_.unite (boxes[j][Y_AXIS]);
272 last_octave = p->get_octave ();
273 last_alteration = p->get_alteration ();
275 ape->left_skyline_ = Skyline (ape->extents_, 0, Y_AXIS, LEFT);
276 ape->right_skyline_ = Skyline (ape->extents_, 0, Y_AXIS, RIGHT);
279 static vector<Grob*>
280 extract_heads_and_stems (vector<Accidental_placement_entry*> const &apes)
282 vector<Grob*> note_cols;
283 vector<Grob*> ret;
285 for (vsize i = apes.size (); i--;)
287 Accidental_placement_entry *ape = apes[i];
288 for (vsize j = ape->grobs_.size (); j--;)
290 Grob *acc = ape->grobs_[j];
291 Grob *head = acc->get_parent (Y_AXIS);
292 Grob *col = head->get_parent (X_AXIS);
294 if (Note_column::has_interface (col))
295 note_cols.push_back (col);
296 else
297 ret.push_back (head);
302 This is a little kludgy: in case there are note columns without
303 accidentals, we get them from the Note_collision objects.
305 for (vsize i = note_cols.size (); i--;)
307 Grob *c = note_cols[i]->get_parent (X_AXIS);
308 if (Note_collision_interface::has_interface (c))
310 extract_grob_set (c, "elements", columns);
311 concat (note_cols, columns);
315 /* Now that we have all of the columns, grab all of the note-heads */
316 for (vsize i = note_cols.size (); i--;)
317 concat (ret, extract_grob_array (note_cols[i], "note-heads"));
319 /* Now that we have all of the heads, grab all of the stems */
320 for (vsize i = ret.size (); i--;)
321 if (Grob *s = Rhythmic_head::get_stem (ret[i]))
322 ret.push_back (s);
325 vector_sort (ret, less<Grob*> ());
326 uniq (ret);
327 return ret;
330 static Grob*
331 common_refpoint_of_accidentals (vector<Accidental_placement_entry*> const &apes, Axis a)
333 Grob *ret = 0;
335 for (vsize i = apes.size (); i--;)
336 for (vsize j = apes[i]->grobs_.size (); j--;)
338 if (!ret)
339 ret = apes[i]->grobs_[j];
340 else
341 ret = ret->common_refpoint (apes[i]->grobs_[j], a);
344 return ret;
347 static Skyline
348 build_heads_skyline (vector<Grob*> const &heads_and_stems,
349 Grob **common)
351 vector<Box> head_extents;
352 for (vsize i = heads_and_stems.size (); i--;)
353 head_extents.push_back (Box (heads_and_stems[i]->extent (common[X_AXIS], X_AXIS),
354 heads_and_stems[i]->pure_height (common[Y_AXIS], 0, INT_MAX)));
356 return Skyline (head_extents, 0, Y_AXIS, LEFT);
360 Position the apes, starting from the right, so that they don't collide.
361 Return the total width.
363 static Interval
364 position_apes (Grob *me,
365 vector<Accidental_placement_entry*> const &apes,
366 Skyline const &heads_skyline)
368 Real padding = robust_scm2double (me->get_property ("padding"), 0.2);
369 Skyline left_skyline = heads_skyline;
370 left_skyline.raise (-robust_scm2double (me->get_property ("right-padding"), 0));
373 Add accs entries right-to-left.
375 Interval width;
376 Real last_offset = 0.0;
377 for (vsize i = apes.size (); i-- > 0;)
379 Accidental_placement_entry *ape = apes[i];
381 Real offset = -ape->right_skyline_.distance (left_skyline);
382 if (isinf (offset))
383 offset = last_offset;
384 else
385 offset -= padding;
387 Skyline new_left_skyline = ape->left_skyline_;
388 new_left_skyline.raise (offset);
389 new_left_skyline.merge (left_skyline);
390 left_skyline = new_left_skyline;
392 /* Shift all of the accidentals in this ape */
393 for (vsize j = ape->grobs_.size (); j--;)
394 ape->grobs_[j]->translate_axis (offset, X_AXIS);
396 for (vsize j = ape->extents_.size (); j--;)
397 width.unite (offset + ape->extents_[j][X_AXIS]);
399 last_offset = offset;
402 return width;
407 This routine computes placements of accidentals. During
408 add_accidental (), accidentals are already grouped by note, so that
409 octaves are placed above each other; they form columns. Then the
410 columns are sorted: the biggest columns go closest to the note.
411 Then the columns are spaced as closely as possible (using skyline
412 spacing).
415 TODO: more advanced placement. Typically, the accs should be placed
416 to form a C shape, like this
425 The naturals should be left of the C as well; they should
426 be separate accs.
428 Note that this placement problem looks NP hard, so we just use a
429 simple strategy, not an optimal choice.
433 TODO: there should be more space in the following situation
436 Natural + downstem
439 * |_
440 * | | X
441 * |_| |
442 * | |
447 MAKE_SCHEME_CALLBACK (Accidental_placement, calc_positioning_done, 1);
449 Accidental_placement::calc_positioning_done (SCM smob)
451 Grob *me = unsmob_grob (smob);
452 if (!me->is_live ())
453 return SCM_BOOL_T;
455 me->set_property ("positioning-done", SCM_BOOL_T);
457 SCM accs = me->get_object ("accidental-grobs");
458 if (!scm_is_pair (accs))
459 return SCM_BOOL_T;
461 vector<Accidental_placement_entry*> apes = build_apes (accs);
463 Grob *common[] = {me, 0};
465 vector<Grob*> heads_and_stems = extract_heads_and_stems (apes);
467 common[Y_AXIS] = common_refpoint_of_accidentals (apes, Y_AXIS);
468 common[Y_AXIS] = common_refpoint_of_array (heads_and_stems, common[Y_AXIS], Y_AXIS);
469 common[X_AXIS] = common_refpoint_of_array (heads_and_stems, me, X_AXIS);
471 for (vsize i = apes.size (); i--;)
472 set_ape_skylines (apes[i], common);
473 Skyline heads_skyline = build_heads_skyline (heads_and_stems, common);
475 stagger_apes (&apes);
476 Interval width = position_apes (me, apes, heads_skyline);
478 me->flush_extent_cache (X_AXIS);
479 me->set_property ("X-extent", ly_interval2scm (width));
481 junk_pointers (apes);
483 return SCM_BOOL_T;
486 ADD_INTERFACE (Accidental_placement,
487 "Resolve accidental collisions.",
489 /* properties */
490 "accidental-grobs "
491 "direction "
492 "left-padding "
493 "padding "
494 "positioning-done "
495 "right-padding "
496 "script-priority "