]> git.donarmstrong.com Git - lilypond.git/blob - lily/accidental-placement.cc
* lily/system.cc (do_derived_mark): don't mark from object_alist_
[lilypond.git] / lily / accidental-placement.cc
1 /*
2   accidental-placement.cc -- implement Accidental_placement
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2002--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include <math.h>
10
11 #include "accidental-placement.hh"
12 #include "libc-extension.hh"    // isinf
13 #include "skyline.hh"
14 #include "music.hh"
15 #include "pitch.hh"
16 #include "warn.hh"
17 #include "note-column.hh"
18 #include "pointer-group-interface.hh"
19 #include "note-collision.hh"
20 #include "accidental-interface.hh"
21
22 MAKE_SCHEME_CALLBACK (Accidental_placement, alignment_callback, 2);
23 SCM
24 Accidental_placement::alignment_callback (SCM s, SCM)
25 {
26   Grob *me = unsmob_grob (s);
27
28   Grob *par = me->get_parent (X_AXIS);
29   if (!to_boolean (par->get_property ("positioning-done")))
30     {
31       par->set_property ("positioning-done", SCM_BOOL_T);
32       position_accidentals (par);
33     }
34
35   return scm_int2num (0);
36 }
37
38 void
39 Accidental_placement::add_accidental (Grob *me, Grob *a)
40 {
41   a->set_parent (me, X_AXIS);
42   a->add_offset_callback (alignment_callback_proc, X_AXIS);
43   SCM cause = a->get_parent (Y_AXIS)->get_property ("cause");
44
45   Music *mcause = unsmob_music (cause);
46   if (!mcause)
47     {
48       programming_error ("note head has no music cause");
49       return;
50     }
51
52   Pitch *p = unsmob_pitch (mcause->get_property ("pitch"));
53
54   int n = p->get_notename ();
55
56   SCM accs = me->get_object ("accidental-grobs");
57   SCM key = scm_int2num (n);
58   SCM entry = scm_assq (key, accs);
59   if (entry == SCM_BOOL_F)
60     {
61       entry = SCM_EOL;
62     }
63   else
64     entry = scm_cdr (entry);
65
66   entry = scm_cons (a->self_scm (), entry);
67
68   accs = scm_assq_set_x (accs, key, entry);
69
70   me->set_object ("accidental-grobs", accs);
71 }
72
73 /*
74   Split into break reminders.
75 */
76 void
77 Accidental_placement::split_accidentals (Grob *accs,
78                                          Link_array<Grob> *break_reminder,
79                                          Link_array<Grob> *real_acc)
80 {
81   for (SCM acs = accs->get_object ("accidental-grobs"); scm_is_pair (acs);
82        acs = scm_cdr (acs))
83     for (SCM s = scm_cdar (acs); scm_is_pair (s); s = scm_cdr (s))
84       {
85         Grob *a = unsmob_grob (scm_car (s));
86
87         if (unsmob_grob (a->get_object ("tie")))
88           break_reminder->push (a);
89         else
90           real_acc->push (a);
91       }
92 }
93
94 /*
95   Accidentals are special, because they appear and disappear after
96   ties at will.
97 */
98 Interval
99 Accidental_placement::get_relevant_accidental_extent (Grob *me,
100                                                       Item *item_col,
101                                                       Grob *left_object)
102 {
103   Link_array<Grob> br, ra;
104   Link_array<Grob> *which = 0;
105
106   Accidental_placement::split_accidentals (me, &br, &ra);
107   br.concat (ra);
108
109   if (dynamic_cast<Item *> (left_object)->break_status_dir () == RIGHT)
110     which = &br;
111   else
112     which = &ra;
113
114   Interval extent;
115   for (int i = 0; i < which->size (); i++)
116     {
117       extent.unite (which->elem (i)->extent (item_col, X_AXIS));
118     }
119
120   if (!extent.is_empty ())
121     {
122       Real p = robust_scm2double (me->get_property ("left-padding"), 0.2);
123       extent[LEFT] -= p;
124     }
125
126   return extent;
127 }
128
129 struct Accidental_placement_entry
130 {
131   Array<Skyline_entry> left_skyline_;
132   Array<Skyline_entry> right_skyline_;
133   Interval vertical_extent_;
134   Array<Box> extents_;
135   Link_array<Grob> grobs_;
136   Real offset_;
137   int notename_;
138   Accidental_placement_entry ()
139   {
140     offset_ = 0.0;
141     notename_ = -1;
142   }
143 };
144
145 static Interval all_accidental_vertical_extent;
146 Real ape_priority (Accidental_placement_entry const *a)
147 {
148   return a->vertical_extent_[UP];
149 }
150
151 int ape_compare (Accidental_placement_entry *const &a,
152                  Accidental_placement_entry *const &b)
153 {
154   return sign (ape_priority (a) - ape_priority (b));
155 }
156
157 int ape_rcompare (Accidental_placement_entry *const &a,
158                   Accidental_placement_entry *const &b)
159 {
160   return -sign (ape_priority (a) - ape_priority (b));
161 }
162
163 /*
164   TODO: should favor
165
166   b
167   b
168
169   placement
170 */
171 void
172 stagger_apes (Link_array<Accidental_placement_entry> *apes)
173 {
174   Link_array<Accidental_placement_entry> asc = *apes;
175
176   asc.sort (&ape_compare);
177
178   apes->clear ();
179
180   int i = 0;
181   int parity = 1;
182   while (i < asc.size ())
183     {
184       Accidental_placement_entry *a = 0;
185       if (parity)
186         a = asc.pop ();
187       else
188         a = asc[i++];
189
190       apes->push (a);
191       parity = !parity;
192     }
193
194   apes->reverse ();
195 }
196
197 /*
198   This routine computes placements of accidentals. During
199   add_accidental (), accidentals are already grouped by note, so that
200   octaves are placed above each other; they form columns. Then the
201   columns are sorted: the biggest columns go closest to the note.
202   Then the columns are spaced as closely as possible (using skyline
203   spacing).
204
205
206   TODO: more advanced placement. Typically, the accs should be placed
207   to form a C shape, like this
208
209
210   ##
211   b b
212   # #
213   b
214   b b
215
216   The naturals should be left of the C as well; they should
217   be separate accs.
218
219   Note that this placement problem looks NP hard, so we just use a
220   simple strategy, not an optimal choice.
221 */
222
223 /*
224   TODO: there should be more space in the following situation
225
226
227   Natural + downstem
228
229   |_
230   | |    X
231   |_|   |
232   |   |
233 */
234 SCM
235 Accidental_placement::position_accidentals (Grob *me)
236 {
237   if (!me->is_live ())
238     return SCM_UNSPECIFIED;
239
240   SCM accs = me->get_object ("accidental-grobs");
241   if (!scm_is_pair (accs))
242     return SCM_UNSPECIFIED;
243
244   /*
245     TODO: there is a bug in this code. If two accs are on the same
246     Y-position, they share an Ape, and will be printed in overstrike.
247   */
248   Link_array<Accidental_placement_entry> apes;
249   for (SCM s = accs; scm_is_pair (s); s = scm_cdr (s))
250     {
251       Accidental_placement_entry *ape = new Accidental_placement_entry;
252       ape->notename_ = scm_to_int (scm_caar (s));
253
254       for (SCM t = scm_cdar (s); scm_is_pair (t); t = scm_cdr (t))
255         ape->grobs_.push (unsmob_grob (scm_car (t)));
256
257       apes.push (ape);
258     }
259
260   Grob *common[] = {me, 0};
261
262   /*
263     First we must extract *all* pointers. We can only determine
264     extents if we're sure that we've found the right common refpoint
265   */
266   Link_array<Grob> note_cols, heads;
267   for (int i = apes.size (); i--;)
268     {
269       Accidental_placement_entry *ape = apes[i];
270       for (int j = ape->grobs_.size (); j--;)
271         {
272           Grob *a = ape->grobs_[j];
273
274           if (common[Y_AXIS])
275             common[Y_AXIS] = common[Y_AXIS]->common_refpoint (a, Y_AXIS);
276           else
277             common[Y_AXIS] = a;
278
279           Grob *head = a->get_parent (Y_AXIS);
280
281           Grob *col = head->get_parent (X_AXIS);
282           if (Note_column::has_interface (col))
283             note_cols.push (col);
284           else
285             heads.push (head);
286         }
287     }
288
289   /*
290     This is a little kludgy: to get all notes, we look if there are
291     collisions as well.
292   */
293   for (int i = note_cols.size (); i--;)
294     {
295       Grob *c = note_cols[i]->get_parent (X_AXIS);
296       if (Note_collision_interface::has_interface (c))
297         {
298           extract_grob_set (c, "elements", gs);
299
300           note_cols.concat (gs);
301         }
302     }
303
304   for (int i = note_cols.size (); i--;)
305     {
306       heads.concat (extract_grob_array (note_cols[i], "note-heads"));
307     }
308
309   heads.default_sort ();
310   heads.uniq ();
311   common[Y_AXIS] = common_refpoint_of_array (heads, common[Y_AXIS], Y_AXIS);
312
313   for (int i = apes.size (); i--;)
314     {
315       Accidental_placement_entry *ape = apes[i];
316       ape->left_skyline_ = empty_skyline (LEFT);
317       ape->right_skyline_ = empty_skyline (RIGHT);
318
319       for (int j = apes[i]->grobs_.size (); j--;)
320         {
321           Grob *a = apes[i]->grobs_[j];
322
323           Array<Box> boxes = Accidental_interface::accurate_boxes (a, common);
324
325           ape->extents_.concat (boxes);
326           for (int j = boxes.size (); j--;)
327             {
328               insert_extent_into_skyline (&ape->left_skyline_, boxes[j], Y_AXIS, LEFT);
329               insert_extent_into_skyline (&ape->right_skyline_, boxes[j], Y_AXIS, RIGHT);
330             }
331         }
332     }
333
334   Interval total;
335   for (int i = apes.size (); i--;)
336     {
337       Interval y;
338
339       for (int j = apes[i]->extents_.size (); j--;)
340         {
341           y.unite (apes[i]->extents_[j][Y_AXIS]);
342         }
343       apes[i]->vertical_extent_ = y;
344       total.unite (y);
345     }
346   all_accidental_vertical_extent = total;
347   stagger_apes (&apes);
348
349   Accidental_placement_entry *head_ape = new Accidental_placement_entry;
350   common[X_AXIS] = common_refpoint_of_array (heads, common[X_AXIS], X_AXIS);
351   Array<Skyline_entry> head_skyline (empty_skyline (LEFT));
352   Array<Box> head_extents;
353   for (int i = heads.size (); i--;)
354     {
355       Box b (heads[i]->extent (common[X_AXIS], X_AXIS),
356              heads[i]->extent (common[Y_AXIS], Y_AXIS));
357
358       insert_extent_into_skyline (&head_skyline, b, Y_AXIS, LEFT);
359     }
360
361   head_ape->left_skyline_ = head_skyline;
362   head_ape->offset_ = 0.0;
363
364   Real padding = robust_scm2double (me->get_property ("padding"), 0.2);
365
366   Array<Skyline_entry> left_skyline = head_ape->left_skyline_;
367   heighten_skyline (&left_skyline,
368                     -robust_scm2double (me->get_property ("right-padding"), 0));
369   /*
370     Add accs entries right-to-left.
371   */
372   for (int i = apes.size (); i-- > 0;)
373     {
374       Real offset
375         = -skyline_meshing_distance (apes[i]->right_skyline_, left_skyline);
376       if (isinf (offset))
377         offset = (i < apes.size () - 1) ? apes[i + 1]->offset_ : 0.0;
378       else
379         offset -= padding;
380
381       apes[i]->offset_ = offset;
382
383       Array<Skyline_entry> new_left_skyline = apes[i]->left_skyline_;
384       heighten_skyline (&new_left_skyline, apes[i]->offset_);
385       merge_skyline (&new_left_skyline, left_skyline, LEFT);
386       left_skyline = new_left_skyline;
387     }
388
389   for (int i = apes.size (); i--;)
390     {
391       Accidental_placement_entry *ape = apes[i];
392       for (int j = ape->grobs_.size (); j--;)
393         {
394           ape->grobs_[j]->translate_axis (ape->offset_, X_AXIS);
395         }
396     }
397
398   Interval left_extent, right_extent;
399   Accidental_placement_entry *ape = apes[0];
400
401   for (int i = ape->extents_.size (); i--;)
402     left_extent.unite (ape->offset_ + ape->extents_[i][X_AXIS]);
403
404   ape = apes.top ();
405   for (int i = ape->extents_.size (); i--;)
406     right_extent.unite (ape->offset_ + ape->extents_[i][X_AXIS]);
407
408   left_extent[LEFT] -= robust_scm2double (me->get_property ("left-padding"), 0);
409   Interval width (left_extent[LEFT], right_extent[RIGHT]);
410
411   SCM scm_width = ly_interval2scm (width);
412   me->set_extent (scm_width, X_AXIS);
413
414   for (int i = apes.size (); i--;)
415     delete apes[i];
416
417   return SCM_UNSPECIFIED;
418 }
419
420 ADD_INTERFACE (Accidental_placement,
421                "accidental-placement-interface",
422                "Resolve accidental collisions.",
423                "left-padding padding right-padding accidental-grobs positioning-done")