]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
''
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7     Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10 */
11
12 #include <math.h>               // m_pi
13
14 #include "lookup.hh"
15 #include "directional-element-interface.hh"
16 #include "note-head.hh"
17 #include "stem.hh"
18 #include "debug.hh"
19 #include "paper-def.hh"
20 #include "rhythmic-head.hh"
21 #include "font-interface.hh"
22 #include "molecule.hh"
23 #include "paper-column.hh"
24 #include "misc.hh"
25 #include "beam.hh"
26 #include "rest.hh"
27 #include "group-interface.hh"
28 #include "staff-symbol-referencer.hh"
29 #include "spanner.hh"
30 #include "side-position-interface.hh"
31 #include "dot-column.hh"
32
33 void
34 Stem::set_beaming (Grob*me ,int i,  Direction d)
35 {
36   SCM pair = me->get_grob_property ("beaming");
37   
38   if (!gh_pair_p (pair))
39     {
40       pair = gh_cons (gh_int2scm (-1),gh_int2scm (-1));
41       me->      set_grob_property ("beaming", pair);
42     }
43   index_set_cell (pair, d, gh_int2scm (i));
44 }
45
46 int
47 Stem::beam_count (Grob*me,Direction d)
48 {
49   SCM p=me->get_grob_property ("beaming");
50   if (gh_pair_p (p))
51     return gh_scm2int (index_cell (p,d));
52   else
53     return -1;
54 }
55
56 Interval
57 Stem::head_positions (Grob*me) 
58 {
59   if (!heads_i (me))
60     {
61       Interval iv;
62       return iv;
63     }
64
65   Drul_array<Grob*> e (extremal_heads (me));
66
67   return Interval (Staff_symbol_referencer::position_f (e[DOWN]),
68                    Staff_symbol_referencer::position_f (e[UP]));
69 }
70
71
72 Real
73 Stem::chord_start_f (Grob*me) 
74 {
75   return head_positions (me)[get_direction (me)]
76     * Staff_symbol_referencer::staff_space (me)/2.0;
77 }
78
79 Real
80 Stem::stem_end_position (Grob*me) 
81 {
82   SCM p =me->get_grob_property ("stem-end-position");
83   Real pos;
84   if (!gh_number_p (p))
85     {
86       pos = get_default_stem_end_position (me);
87       me->set_grob_property ("stem-end-position", gh_double2scm (pos));
88     }
89   else
90     pos = gh_scm2double (p);
91
92   return pos;
93 }
94
95 Direction
96 Stem::get_direction (Grob*me)
97 {
98   Direction d = Directional_element_interface::get (me);
99
100   if (!d)
101     {
102        d = get_default_dir (me);
103        // urg, AAARGH!
104        Directional_element_interface::set (me, d);
105     }
106   return d ;
107 }
108
109
110 void
111 Stem::set_stemend (Grob*me, Real se)
112 {
113   // todo: margins
114   Direction d= get_direction (me);
115   
116   if (d && d * head_positions (me)[get_direction (me)] >= se*d)
117     me->warning (_ ("Weird stem size; check for narrow beams"));
118
119   me->set_grob_property ("stem-end-position", gh_double2scm (se));
120 }
121
122 int
123 Stem::type_i (Grob*me) 
124 {
125   return first_head (me) ?  Rhythmic_head::balltype_i (first_head (me)) : 2;
126 }
127
128 /*
129   Note head that determines hshift for upstems
130  */ 
131 Grob*
132 Stem::support_head (Grob*me)
133 {
134   SCM h = me->get_grob_property ("support-head");
135   Grob * nh = unsmob_grob (h);
136   if (nh)
137     return nh;
138   else if (heads_i (me) == 1)
139     {
140       /*
141         UGH.
142        */
143       
144       return unsmob_grob (ly_car (me->get_grob_property ("heads")));
145     }
146   else
147     return first_head (me);
148 }
149
150
151 int
152 Stem::heads_i (Grob*me)
153 {
154   return  Pointer_group_interface::count (me, "heads");
155 }
156
157 /*
158   The note head which forms one end of the stem.  
159  */
160 Grob*
161 Stem::first_head (Grob*me)
162 {
163   return extremal_heads (me)[-get_direction (me)];
164 }
165
166 /*
167   START is part where stem reaches `last' head. 
168  */
169 Drul_array<Grob*>
170 Stem::extremal_heads (Grob*me) 
171 {
172   const int inf = 1000000;
173   Drul_array<int> extpos;
174   extpos[DOWN] = inf;
175   extpos[UP] = -inf;  
176   
177   Drul_array<Grob *> exthead;
178   exthead[LEFT] = exthead[RIGHT] =0;
179   
180   for (SCM s = me->get_grob_property ("heads"); gh_pair_p (s); s = ly_cdr (s))
181     {
182       Grob * n = unsmob_grob (ly_car (s));
183
184       
185       int p = int (Staff_symbol_referencer::position_f (n));
186
187       Direction d = LEFT;
188       do {
189       if (d* p > d* extpos[d])
190         {
191           exthead[d] = n;
192           extpos[d] = p;
193         }
194       } while (flip (&d) != DOWN);
195     }
196
197   return exthead;
198 }
199
200 static int
201 icmp (int const &a, int const &b)
202 {
203   return a-b;
204 }
205
206 Array<int>
207 Stem::note_head_positions (Grob *me)
208 {
209   Array<int> ps ;
210   for (SCM s = me->get_grob_property ("heads"); gh_pair_p (s); s = ly_cdr (s))
211     {
212       Grob * n = unsmob_grob (ly_car (s));
213       int p = int (Staff_symbol_referencer::position_f (n));
214
215       ps.push (p);
216     }
217
218   ps.sort (icmp);
219   return ps; 
220 }
221
222
223 void
224 Stem::add_head (Grob*me, Grob *n)
225 {
226   n->set_grob_property ("stem", me->self_scm ());
227   n->add_dependency (me);
228
229   if (Note_head::has_interface (n))
230     {
231       Pointer_group_interface::add_grob (me, ly_symbol2scm ("heads"), n);
232     }
233 }
234
235 bool
236 Stem::invisible_b (Grob*me)
237 {
238   return ! (heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
239 }
240
241 int
242 Stem::get_center_distance (Grob*me, Direction d)
243 {
244   int staff_center = 0;
245   int distance = (int) (d* (head_positions (me)[d] - staff_center));
246   return distance >? 0;
247 }
248
249 Direction
250 Stem::get_default_dir (Grob*me) 
251 {
252   int du = get_center_distance (me,UP);
253   int dd = get_center_distance (me,DOWN);
254
255   if (sign (dd - du))
256     return Direction (sign (dd -du));
257
258   return to_dir (me->get_grob_property ("neutral-direction"));
259 }
260
261 Real
262 Stem::get_default_stem_end_position (Grob*me) 
263 {
264   bool grace_b = to_boolean (me->get_grob_property ("grace"));
265   SCM s;
266   Array<Real> a;
267
268   Real length_f = 0.;
269   SCM scm_len = me->get_grob_property ("length");
270   if (gh_number_p (scm_len))
271     {
272       length_f = gh_scm2double (scm_len);
273     }
274   else
275     {
276       s = me->get_grob_property ("lengths");
277       for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
278         a.push (gh_scm2double (ly_car (q)));
279                 
280       // stem uses half-spaces
281       length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
282     }
283
284
285   a.clear ();
286   s = me->get_grob_property ("stem-shorten");
287   for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
288     a.push (gh_scm2double (ly_car (q)));
289
290
291   // stem uses half-spaces
292
293   // fixme: use scm_list_n_ref () iso. array[]
294   Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
295
296   /* On boundary: shorten only half */
297   if (abs (chord_start_f (me)) == 0.5)
298     shorten_f *= 0.5;
299
300   /* URGURGURG
301      'set-default-stemlen' sets direction too
302    */
303   Direction dir = get_direction (me);
304   if (!dir)
305     {
306       dir = get_default_dir (me);
307       Directional_element_interface::set (me, dir);
308     }
309   
310   /* stems in unnatural (forced) direction should be shortened, 
311     according to [Roush & Gourlay] */
312   if (chord_start_f (me)
313       && (get_direction (me) != get_default_dir (me)))
314     length_f -= shorten_f;
315
316   Interval hp = head_positions (me);  
317   Real st = hp[dir] + dir * length_f;
318
319   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
320   if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
321     st = 0.0;
322
323   /*
324     Make a little room if we have a upflag and there is a dot.
325     previous approach was to lengthen the stem. This is not
326     good typesetting practice. 
327     
328   */
329   if (!beam_l (me) && dir == UP
330       && duration_log (me) > 2)
331     {
332       Grob * closest_to_flag = extremal_heads (me)[dir];
333       Grob * dots = closest_to_flag
334         ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
335
336       if (dots)
337         {
338           Real dp = Staff_symbol_referencer::position_f  (dots);
339           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
340             / Staff_symbol_referencer::staff_space (me); 
341
342           /*
343             Very gory: add myself to the X-support of the parent,
344             which should be a dot-column.
345            */
346           if (dir * (st + flagy -  dp) < 0.5)
347             {
348               Grob *par = dots->get_parent (X_AXIS);
349
350               if (Dot_column::has_interface (par))
351                 {
352                   Side_position_interface::add_support (par, me);
353
354                   /*
355                     TODO: apply some better logic here. The flag is
356                     curved inwards, so this will typically be too
357                     much.
358                   */
359                 }
360             }
361         }
362     }
363
364
365   return st;
366 }
367
368
369
370 /*
371   
372   the log of the duration (Number of hooks on the flag minus two)
373  */
374 int
375 Stem::duration_log (Grob*me) 
376 {
377   SCM s = me->get_grob_property ("duration-log");
378   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
379 }
380
381 void
382 Stem::position_noteheads (Grob*me)
383 {
384   if (!heads_i (me))
385     return;
386   
387   Link_array<Grob> heads =
388     Pointer_group_interface__extract_grobs (me, (Grob*)0, "heads");
389
390   heads.sort (compare_position);
391   Direction dir =get_direction (me);
392   
393   if (dir < 0)
394     heads.reverse ();
395
396
397   Grob *hed = support_head (me);
398   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
399   for (int i=0; i < heads.size (); i++)
400     {
401       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
402                                 X_AXIS);
403     }
404   
405   bool parity= true;            // todo: make me settable.
406   int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
407   for (int i=1; i < heads.size (); i ++)
408     {
409       Real p = Staff_symbol_referencer::position_f (heads[i]);
410       int dy =abs (lastpos- (int)p);
411
412       if (dy <= 1)
413         {
414           if (parity)
415             {
416               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
417
418               heads[i]->translate_axis (l * get_direction (me), X_AXIS);
419             }
420           parity = !parity;
421         }
422       else
423         parity = true;
424       
425       lastpos = int (p);
426     }
427 }
428
429 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
430 SCM
431 Stem::before_line_breaking (SCM smob)
432 {
433   Grob*me = unsmob_grob (smob);
434   stem_end_position (me);       // ugh. Trigger direction calc.
435   position_noteheads (me);
436
437   if (invisible_b (me))
438     {
439       me->remove_grob_property ("molecule-callback");
440       // suicide ();
441     }
442   
443   return SCM_UNSPECIFIED;
444 }
445
446 /*
447   ugh.
448   When in a beam with tuplet brackets, brew_mol is called early,
449   caching a wrong value.
450  */
451 MAKE_SCHEME_CALLBACK (Stem, height, 2);
452 SCM
453 Stem::height (SCM smob, SCM ax)
454 {
455   Axis a = (Axis)gh_scm2int (ax);
456   Grob * me = unsmob_grob (smob);
457   assert (a == Y_AXIS);
458
459   SCM mol = me->get_uncached_molecule ();
460   Interval iv;
461   if (mol != SCM_EOL)
462     iv = unsmob_molecule (mol)->extent (a);
463   return ly_interval2scm (iv);
464 }
465
466
467 Molecule
468 Stem::flag (Grob*me)
469 {
470   /* TODO: rename flag-style into something more appropriate,
471    e.g. "stroke-style", maybe with values "" (i.e. no stroke),
472    "single" and "double".  Needs more discussion.
473   */
474   String style, fstyle, staffline_offs;
475   SCM fst = me->get_grob_property ("flag-style");
476   if (gh_string_p (fst))
477     {
478       fstyle = ly_scm2string (fst);
479     }
480
481   SCM st = me->get_grob_property ("style");
482   if (gh_symbol_p (st))
483     {
484       style = (ly_scm2string (scm_symbol_to_string (st)));
485     }
486   else
487     {
488       style = "";
489     }
490   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
491
492   if (String::compare_i (style, "mensural") == 0)
493     /* Mensural notation: For notes on staff lines, use different
494        flags than for notes between staff lines.  The idea is that
495        flags are always vertically aligned with the staff lines,
496        regardless if the note head is on a staff line or between two
497        staff lines.  In other words, the inner end of a flag always
498        touches a staff line.
499     */
500     {
501       if (adjust)
502         {
503           /* Urrgh!  We have to detect wether this stem ends on a staff
504              line or between two staff lines.  But we can not call
505              stem_end_position(me) or get_default_stem_end_position(me),
506              since this encounters the flag and hence results in an
507              infinite recursion.  However, in pure mensural notation,
508              there are no multiple note heads attached to a single stem,
509              neither is there usually need for using the stem_shorten
510              property (except for 32th and 64th notes, but that is not a
511              problem since the stem length in this case is augmented by
512              an integral multiple of staff_space).  Hence, it should be
513              sufficient to just take the first note head, assume it's
514              the only one, look if it's on a staff line, and select the
515              flag's shape accordingly.  In the worst case, the shape
516              looks slightly misplaced, but that will usually be the
517              programmer's fault (e.g. when trying to attach multiple
518              note heads to a single stem in mensural notation).  */
519
520           /*
521             perhaps the detection whether this correction is needed should
522             happen in a different place  to avoid the recursion.
523             
524             --hwn.
525           */
526           Grob *first = first_head(me);
527           int sz = Staff_symbol_referencer::line_count (me)-1;
528           int p = (int)rint (Staff_symbol_referencer::position_f (first));
529           staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
530         }
531       else
532         {
533           staffline_offs = "2";
534         }
535     }
536   else
537     {
538       staffline_offs = "";
539     }
540   char c = (get_direction (me) == UP) ? 'u' : 'd';
541   String index_str
542     = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
543   Molecule m
544     = Font_interface::get_default_font (me)->find_by_name (index_str);
545   if (!fstyle.empty_b ())
546     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
547   return m;
548 }
549
550 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
551 SCM
552 Stem::dim_callback (SCM e, SCM ax)
553 {
554   Axis a = (Axis) gh_scm2int (ax);
555   assert (a == X_AXIS);
556   Grob *se = unsmob_grob (e);
557   Interval r (0, 0);
558   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
559     ;   // TODO!
560   else
561     {
562       r = flag (se).extent (X_AXIS);
563     }
564   return ly_interval2scm (r);
565 }
566  
567
568
569 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
570
571 SCM
572 Stem::brew_molecule (SCM smob) 
573 {
574   Grob*me = unsmob_grob (smob);
575   Molecule mol;
576   Direction d = get_direction (me);
577   
578   
579   Real y1 = Staff_symbol_referencer::position_f (first_head (me));
580   Real y2 = stem_end_position (me);
581   
582   Interval stem_y (y1 <? y2,y2 >? y1);
583
584
585   // dy?
586   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
587
588   if (Grob *hed = support_head (me))
589     {
590       /*
591         must not take ledgers into account.
592        */
593       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
594       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
595
596       y_attach = head_height.linear_combination (y_attach);
597       stem_y[Direction (-d)] += d * y_attach/dy;
598     }
599   
600   if (!invisible_b (me))
601     {
602       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
603         // URG
604         * me->paper_l ()->get_var ("linethickness");
605       
606       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
607                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
608       mol.add_molecule (ss);
609     }
610
611   if (!beam_l (me) && abs (duration_log (me)) > 2)
612     {
613       Molecule fl = flag (me);
614       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
615       mol.add_molecule (fl);
616     }
617
618   return mol.smobbed_copy ();
619 }
620
621 /*
622   move the stem to right of the notehead if it is up.
623  */
624 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
625 SCM
626 Stem::off_callback (SCM element_smob, SCM)
627 {
628   Grob *me = unsmob_grob (element_smob);
629   
630   Real r=0;
631   if (Grob * f = first_head (me))
632     {
633       Interval head_wid = Note_head::head_extent(f, X_AXIS);
634
635       Real attach =
636         Note_head::stem_attachment_coordinate(f, X_AXIS);
637
638       Direction d = get_direction (me);
639
640       Real real_attach = head_wid.linear_combination (d * attach);
641
642       r = real_attach;
643
644       /*
645         If not centered: correct for stem thickness.
646        */
647       if (attach)
648         {
649           Real rule_thick
650             = gh_scm2double (me->get_grob_property ("thickness"))
651             * me->paper_l ()->get_var ("linethickness");
652
653           
654           r += - d * rule_thick * 0.5;
655         }
656     }
657   return gh_double2scm (r);
658 }
659
660
661
662 Grob*
663 Stem::beam_l (Grob*me)
664 {
665   SCM b=  me->get_grob_property ("beam");
666   return unsmob_grob (b);
667 }
668
669
670 // ugh still very long.
671 Stem_info
672 Stem::calc_stem_info (Grob*me) 
673 {
674   SCM scm_info = me->get_grob_property ("stem-info");
675
676   if (gh_pair_p (scm_info ))
677     {
678       Stem_info si ;
679
680       si.ideal_y = gh_scm2double (gh_car (scm_info)); 
681       si.max_y = gh_scm2double (gh_cadr (scm_info)); 
682       si.min_y = gh_scm2double (gh_caddr (scm_info));
683
684       return si;
685     }
686     
687   Grob * beam = beam_l (me);
688
689   Direction beam_dir = Directional_element_interface::get (beam);
690   if (!beam_dir)
691     {
692       programming_error ("Beam dir not set.");
693       beam_dir = UP;
694     }
695     
696
697   Real staff_space = Staff_symbol_referencer::staff_space (me);
698   Real half_space = staff_space / 2;
699   
700   int multiplicity = Beam::get_multiplicity (beam);
701   Real interbeam_f = Beam::get_interbeam (beam);
702
703   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
704   Stem_info info; 
705   info.ideal_y = chord_start_f (me);
706
707   // for simplicity, we calculate as if dir == UP
708   info.ideal_y *= beam_dir;
709   SCM grace_prop = me->get_grob_property ("grace");
710
711   bool grace_b = to_boolean (grace_prop);
712   
713   Array<Real> a;
714   SCM s;
715   
716   s = me->get_grob_property ("beamed-minimum-lengths");
717   a.clear ();
718   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
719     a.push (gh_scm2double (ly_car (q)));
720
721
722   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
723   s = me->get_grob_property ("beamed-lengths");
724
725   a.clear ();
726   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
727     a.push (gh_scm2double (ly_car (q)));
728
729   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
730
731   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
732     /* normal beamed stem */
733     {
734       if (multiplicity)
735         {
736           info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
737         }
738       info.min_y = info.ideal_y;
739       info.max_y = 1000;  // INT_MAX;
740
741       info.ideal_y += stem_length;
742       info.min_y += minimum_length;
743
744       /*
745         lowest beam of (UP) beam must never be lower than second staffline
746
747         Hmm, reference (Wanske?)
748
749         Although this (additional) rule is probably correct,
750         I expect that highest beam (UP) should also never be lower
751         than middle staffline, just as normal stems.
752         
753       */
754       bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
755       if (!grace_b && !no_extend_b)
756         {
757           /* highest beam of (UP) beam must never be lower than middle
758              staffline
759              lowest beam of (UP) beam must never be lower than second staffline
760            */
761           info.min_y =
762             info.min_y >? 0
763             >? (- 2 * half_space - thick
764                 + (multiplicity > 0) * thick
765                 + interbeam_f * (multiplicity - 1));
766         }
767     }
768   else
769     /* knee */
770     {
771       info.ideal_y -= thick;
772       info.max_y = info.ideal_y;
773       info.min_y = - 1000 ; // INT_MAX;
774
775       info.ideal_y -= stem_length;
776       info.max_y -= minimum_length;
777     }
778   
779   info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
780
781   s = beam->get_grob_property ("shorten");
782   if (gh_number_p (s))
783     info.ideal_y -= gh_scm2double (s);
784
785   Grob *common = me->common_refpoint (beam, Y_AXIS);
786   Real interstaff_f = beam_dir *
787     (me->relative_coordinate (common, Y_AXIS)
788      - beam->relative_coordinate (common, Y_AXIS));
789   
790   info.ideal_y += interstaff_f;
791   info.min_y += interstaff_f;
792   info.max_y += interstaff_f ;
793
794   me->set_grob_property ("stem-info",
795                          scm_list_n (gh_double2scm (info.ideal_y),
796                                      gh_double2scm (info.max_y),
797                                      gh_double2scm (info.min_y),
798                                      SCM_UNDEFINED));
799   
800   return info;
801 }
802
803 bool
804 Stem::has_interface (Grob*m)
805 {
806   return m && m->has_interface (ly_symbol2scm ("stem-interface"));
807 }
808
809 ADD_INTERFACE (Stem,"stem-interface",
810   "A stem",
811   "adjust-if-on-staffline thickness stem-info beamed-lengths beamed-minimum-lengths lengths beam stem-shorten duration-log beaming neutral-direction stem-end-position support-head heads direction length style no-stem-extend flag-style dir-forced");
812