]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-collision.cc
1432c27564c9c7a843ef6f09704d376f766b693e
[lilypond.git] / lily / note-collision.cc
1 /*
2   collision.cc -- implement Collision
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include <math.h>
10
11 #include "warn.hh"
12 #include "note-collision.hh"
13 #include "note-column.hh"
14 #include "note-head.hh"
15 #include "rhythmic-head.hh"
16 #include "paper-def.hh"
17 #include "axis-group-interface.hh"
18 #include "item.hh"
19 #include "stem.hh"
20 #include "side-position-interface.hh"
21 #include "dot-column.hh"
22
23 MAKE_SCHEME_CALLBACK (Note_collision_interface,force_shift_callback,2);
24
25 SCM
26 Note_collision_interface::force_shift_callback (SCM element_smob, SCM axis)
27 {
28   Grob *me = unsmob_grob (element_smob);
29   Axis a = (Axis) gh_scm2int (axis);
30   assert (a == X_AXIS);
31   
32    me = me->get_parent (a);
33
34    if (! to_boolean (me->get_property ("positioning-done")))
35     {
36       me->set_property ("positioning-done", SCM_BOOL_T);
37       do_shifts (me);
38     }
39   
40   return gh_double2scm (0.0);
41 }
42
43
44 void
45 check_meshing_chords (Grob *me,
46                       Drul_array< Array < Real > > *offsets,
47                       Drul_array< Array < Slice > > const &extents,
48                       Drul_array<Link_array<Grob> > const &clash_groups)
49         
50 {
51   if (!extents[UP].size () || ! extents[DOWN].size ())
52     return;
53   
54   Grob *cu = clash_groups[UP][0];
55   Grob *cd = clash_groups[DOWN][0];
56
57   /* Every note column should have a stem, but avoid a crash. */
58   if (!Note_column::get_stem (cu) || !Note_column::get_stem (cd))
59     return;
60
61   Grob *nu = Note_column::first_head (cu);
62   Grob *nd = Note_column::first_head (cd);
63
64   Array<int> ups = Stem::note_head_positions (Note_column::get_stem (cu));
65   Array<int> dps = Stem::note_head_positions (Note_column::get_stem (cd));
66
67   /* Too far apart to collide.  */
68   if (ups[0] > dps.top () + 1)
69     return; 
70
71   // FIXME: what's this?
72   bool merge_possible = (ups[0] >= dps[0]) && (ups.top () >= dps.top ());
73
74   int upball_type = Note_head::get_balltype (nu);
75   int dnball_type = Note_head::get_balltype (nd);
76   
77   /* Do not merge whole notes (or longer, like breve, longa, maxima).  */
78   if (merge_possible && (upball_type <= 0 || dnball_type <= 0))
79     merge_possible = false;
80
81   if (merge_possible
82       && Rhythmic_head::dot_count (nu) != Rhythmic_head::dot_count (nd)
83       && !to_boolean (me->get_property ("merge-differently-dotted")))
84     merge_possible = false;
85
86   /* Can only merge different heads if merge-differently-headed is
87      set. */
88   if (merge_possible
89       && upball_type != dnball_type
90       && !to_boolean (me->get_property ("merge-differently-headed")))
91     merge_possible = false;
92
93   /* Should never merge quarter and half notes, as this would make
94      them indistinguishable.  */
95   if (merge_possible
96       && ((Rhythmic_head::duration_log (nu) == 1
97            && Rhythmic_head::duration_log (nd) == 2)
98           || (Rhythmic_head::duration_log (nu) == 2
99              && Rhythmic_head::duration_log (nd) == 1)))
100     merge_possible = false;
101
102
103   /*
104     this case (distant half collide), 
105     
106         |
107       x |
108      | x
109      |
110
111    the noteheads may be closer than this case (close half collide)
112
113        |
114        |
115       x 
116      x
117     |
118     |
119     
120    */
121   
122   /* TODO: filter out the 'o's in this configuration, since they're no
123   part in the collision.
124
125      |
126     x|o
127     x|o
128     x
129     
130    */
131   
132   bool close_half_collide = false;
133   bool distant_half_collide = false;  
134   bool full_collide = false;  
135
136   int i = 0, j=0;
137   while (i < ups.size () && j < dps.size ())
138   {
139     if (abs (ups[i] - dps[j]) == 1)
140       {
141         merge_possible = false;
142         if (ups[i] > dps[j])
143           close_half_collide = true;
144         else
145           distant_half_collide = true;
146       }
147     else if (ups[i]==dps[j])
148       full_collide = true;
149     else if (ups[i] >dps[0] && ups[i] < dps.top ())
150       merge_possible = false;
151     else if (dps[j] >ups[0] && dps[j] < ups.top ())
152       merge_possible = false;
153     
154     if (ups[i] < dps[j])
155       i++;
156     else if (ups[i] > dps[j])
157       j++;
158     else
159       {
160         i++;
161         j++;
162       }
163   }
164
165   full_collide = full_collide || (close_half_collide
166                                   && distant_half_collide);
167   
168   Drul_array<Real> center_note_shifts;
169   center_note_shifts[LEFT] = 0.0;
170   center_note_shifts[RIGHT] = 0.0;
171
172   
173   Real shift_amount = 1;
174
175   bool touch = (ups[0] >= dps.top ());
176   if (touch)
177     shift_amount *= -1;
178
179   /* For full collisions, the right hand head may obscure dots, so
180      make sure the dotted heads go to the right.  */
181   bool stem_to_stem = false;
182   if (full_collide)
183     if (Rhythmic_head::dot_count (nu) > Rhythmic_head::dot_count (nd))
184       shift_amount = 1;
185     else if (Rhythmic_head::dot_count (nu) < Rhythmic_head::dot_count (nd))
186       stem_to_stem = true;
187   
188   if (merge_possible)
189     {
190       shift_amount = 0;
191
192
193       /* If possible, don't wipe any heads. Else, wipe shortest head,
194          or head with smallest amount of dots.  Note: when merging
195          different heads, dots on the smaller one disappear. */
196       Grob *wipe_ball = 0;
197       Grob *dot_wipe_head = nu;
198       
199       if (upball_type == dnball_type)
200         {
201           if (Rhythmic_head::dot_count (nd) < Rhythmic_head::dot_count (nu))
202             {
203               wipe_ball = nd;
204               dot_wipe_head = nd;
205             }
206           else if (Rhythmic_head::dot_count (nd) > Rhythmic_head::dot_count (nu))
207             {
208               dot_wipe_head = nu;
209               wipe_ball = nu;
210             }
211           else
212             {
213               dot_wipe_head = nu;
214             }
215         }
216       else if (dnball_type > upball_type)
217         {
218           wipe_ball = nd;
219           dot_wipe_head = nd;
220         }
221       else if (dnball_type < upball_type)
222         {
223           wipe_ball = nu;
224           dot_wipe_head = nu;
225         }
226
227       if (dot_wipe_head)
228         {
229           if (Grob *d = unsmob_grob (dot_wipe_head->get_property ("dot")))
230             d->suicide ();
231         }
232       
233       if (wipe_ball && wipe_ball->live ())
234         {
235           wipe_ball->set_property ("transparent", SCM_BOOL_T);
236           wipe_ball->set_property ("stencil", SCM_EOL);
237         }
238     }
239   /* TODO: these numbers are magic; should devise a set of grob props
240      to tune this behavior.  */
241   else if (stem_to_stem)
242     shift_amount = -abs (shift_amount) * 0.65; 
243   else if (close_half_collide && !touch)
244     shift_amount *= 0.52;
245   else if (distant_half_collide && !touch)
246     shift_amount *= 0.4;
247   else if (distant_half_collide || close_half_collide || full_collide)
248     shift_amount *= 0.5;
249   
250   /* we're meshing.  */
251   else if (Rhythmic_head::dot_count (nu) || Rhythmic_head::dot_count (nd))
252     shift_amount *= 0.1;
253   else
254     shift_amount *= 0.25;
255
256   /* For full or close half collisions, the right hand head may
257      obscure dots.  Move dots to the right.  */
258   if (abs (shift_amount) > 1e-6
259       && Rhythmic_head::dot_count (nd) > Rhythmic_head::dot_count (nu)
260       && (full_collide || close_half_collide))
261     {
262       Grob *d = unsmob_grob (nd->get_property ("dot"));
263       Grob *parent = d->get_parent (X_AXIS);
264       if (Dot_column::has_interface (parent))
265         Side_position_interface::add_support (parent, nu);
266     }
267
268   Direction d = UP;
269   do
270     {
271       for (int i=0; i < clash_groups[d].size (); i++)
272         (*offsets)[d][i] += d * shift_amount;
273     }
274   while ((flip (&d))!= UP);
275 }
276
277 void
278 Note_collision_interface::do_shifts (Grob* me)
279 {
280   Drul_array< Link_array <Grob>  > cg = get_clash_groups (me);
281
282   SCM autos (automatic_shift (me, cg));
283   SCM hand (forced_shift (me));
284   
285   Direction d = UP;
286   Real wid = 0.0;
287   do
288     {
289       if (cg[d].size ())
290         {
291           Grob  *h = cg[d][0];
292           wid = Note_column::first_head (h)->extent (h,X_AXIS).length () ;
293         }
294     }
295   while (flip (&d) != UP);
296   
297   Link_array<Grob> done;
298   for (; gh_pair_p (hand); hand =ly_cdr (hand))
299     {
300       Grob * s = unsmob_grob (ly_caar (hand));
301       Real amount = gh_scm2double (ly_cdar (hand));
302       
303       s->translate_axis (amount *wid, X_AXIS);
304       done.push (s);
305     }
306   for (; gh_pair_p (autos); autos =ly_cdr (autos))
307     {
308       Grob * s = unsmob_grob (ly_caar (autos));
309       Real amount = gh_scm2double (ly_cdar (autos));
310       
311       if (!done.find (s))
312         s->translate_axis (amount * wid, X_AXIS);
313     }
314 }
315
316 Drul_array< Link_array <Grob>  >
317 Note_collision_interface::get_clash_groups (Grob *me)
318 {
319   Drul_array<Link_array<Grob> > clash_groups;
320  
321   SCM s = me->get_property ("elements");
322   for (; gh_pair_p (s); s = ly_cdr (s))
323     {
324       SCM car = ly_car (s);
325
326       Grob * se = unsmob_grob (car);
327       if (Note_column::has_interface (se))
328         clash_groups[Note_column::dir (se)].push (se);
329     }
330   
331   Direction d = UP;
332   do
333     {
334       Link_array<Grob> & clashes (clash_groups[d]);
335       clashes.sort (Note_column::shift_compare);
336     }
337   while ((flip (&d))!= UP);
338
339   return clash_groups;
340 }
341
342 /** This complicated routine moves note columns around horizontally to
343   ensure that notes don't clash.
344
345   This should be put into Scheme.  
346   */
347 SCM
348 Note_collision_interface::automatic_shift (Grob *me,
349                             Drul_array< Link_array <Grob> > 
350                             clash_groups)
351 {
352   Drul_array<Array<int> > shifts;
353   SCM  tups = SCM_EOL;
354
355   
356   Direction d = UP;
357   do
358     {
359       Array<int> & shift (shifts[d]);
360       Link_array<Grob> & clashes (clash_groups[d]);
361
362       for (int i=0; i < clashes.size (); i++)
363         {
364           SCM sh
365             = clashes[i]->get_property ("horizontal-shift");
366
367           if (gh_number_p (sh))
368             shift.push (gh_scm2int (sh));
369           else
370             shift.push (0);
371         }
372       
373       for (int i=1; i < shift.size (); i++)
374         {
375           if (shift[i-1] == shift[i])
376             {
377               clashes[0]->warning (_ ("Too many clashing notecolumns.  Ignoring them."));
378               return tups;
379             }
380         }
381     }
382   while ((flip (&d))!= UP);
383
384   Drul_array< Array < Slice > > extents;
385   Drul_array< Array < Real > > offsets;
386   d = UP;
387   do
388     {
389       for (int i=0; i < clash_groups[d].size (); i++)
390         {
391           Slice s (Note_column::head_positions_interval (clash_groups[d][i]));
392           s[LEFT] --;
393           s[RIGHT]++;
394           extents[d].push (s);
395           offsets[d].push (d * 0.5 * i);
396         }
397     }
398   while ((flip (&d))!= UP);
399
400   /*
401     do horizontal shifts of each direction 
402
403        | 
404       x||
405        x||
406         x|
407    */
408   
409   do
410     {
411       for (int i=1; i < clash_groups[d].size (); i++)
412         {
413           Slice prev =extents[d][i-1];
414           prev.intersect (extents[d][i]);
415           if (prev.length ()> 0 ||
416  (extents[-d].size () && d * (extents[d][i][-d] - extents[-d][0][d]) < 0))
417             for (int j = i; j <  clash_groups[d].size (); j++)
418               offsets[d][j] += d * 0.5;
419         }
420     }   
421   while ((flip (&d))!= UP);
422
423
424   /*
425     Check if chords are meshing
426    */
427
428   check_meshing_chords (me, &offsets, extents, clash_groups);
429   
430   do
431     {
432       for (int i=0; i < clash_groups[d].size (); i++)
433         tups = gh_cons (gh_cons (clash_groups[d][i]->self_scm (),
434                                  gh_double2scm (offsets[d][i])),
435                         tups);
436     }
437   while (flip (&d) != UP);
438   return tups;
439 }
440
441
442 SCM
443 Note_collision_interface::forced_shift (Grob *me)
444 {
445   SCM tups = SCM_EOL;
446   
447   SCM s = me->get_property ("elements");
448   for (; gh_pair_p (s); s = ly_cdr (s))
449     {
450       Grob * se = unsmob_grob (ly_car (s));
451
452       SCM force =  se->get_property ("force-hshift");
453       if (gh_number_p (force))
454         {
455           tups = gh_cons (gh_cons (se->self_scm (), force),
456                           tups);
457         }
458     }
459   return tups;
460 }
461
462 void
463 Note_collision_interface::add_column (Grob*me,Grob* ncol)
464 {
465   ncol->add_offset_callback (Note_collision_interface::force_shift_callback_proc, X_AXIS);
466   Axis_group_interface::add_element (me, ncol);
467   me->add_dependency (ncol);
468 }
469
470
471 ADD_INTERFACE (Note_collision_interface, "note-collision-interface",
472                "An object that handles collisions between notes with different stem " 
473                "directions and horizontal shifts. Most of the interesting properties "
474                "are to be set in @ref{note-column-interface}: these are "
475                "@code{force-hshift} and @code{horizontal-shift}."
476
477                ,
478                
479                "merge-differently-dotted merge-differently-headed positioning-done");