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