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