]> 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 Direction
242 Stem::get_default_dir (Grob*me) 
243 {
244   int staff_center = 0;
245   Interval hp = head_positions (me);
246   if (hp.empty_b())
247     {
248       return CENTER;
249     }
250   
251   int udistance = (int) (UP * hp[UP] - staff_center);
252   int ddistance = (int) (DOWN* hp[DOWN] - staff_center);  
253   
254   if (sign (ddistance - udistance))
255     return Direction (sign (ddistance -udistance));
256
257   return to_dir (me->get_grob_property ("neutral-direction"));
258 }
259
260 Real
261 Stem::get_default_stem_end_position (Grob*me) 
262 {
263   bool grace_b = to_boolean (me->get_grob_property ("grace"));
264   SCM s;
265   Array<Real> a;
266
267   Real length_f = 0.;
268   SCM scm_len = me->get_grob_property ("length");
269   if (gh_number_p (scm_len))
270     {
271       length_f = gh_scm2double (scm_len);
272     }
273   else
274     {
275       s = me->get_grob_property ("lengths");
276       for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
277         a.push (gh_scm2double (ly_car (q)));
278                 
279       // stem uses half-spaces
280       length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
281     }
282
283
284   a.clear ();
285   s = me->get_grob_property ("stem-shorten");
286   for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
287     a.push (gh_scm2double (ly_car (q)));
288
289
290   // stem uses half-spaces
291
292   // fixme: use scm_list_n_ref () iso. array[]
293   Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
294
295   /* On boundary: shorten only half */
296   if (abs (chord_start_f (me)) == 0.5)
297     shorten_f *= 0.5;
298
299   /* URGURGURG
300      'set-default-stemlen' sets direction too
301    */
302   Direction dir = get_direction (me);
303   if (!dir)
304     {
305       dir = get_default_dir (me);
306       Directional_element_interface::set (me, dir);
307     }
308   
309   /* stems in unnatural (forced) direction should be shortened, 
310     according to [Roush & Gourlay] */
311   if (chord_start_f (me)
312       && (get_direction (me) != get_default_dir (me)))
313     length_f -= shorten_f;
314
315   Interval hp = head_positions (me);  
316   Real st = hp[dir] + dir * length_f;
317
318   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
319   if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
320     st = 0.0;
321
322   /*
323     Make a little room if we have a upflag and there is a dot.
324     previous approach was to lengthen the stem. This is not
325     good typesetting practice. 
326     
327   */
328   if (!beam_l (me) && dir == UP
329       && duration_log (me) > 2)
330     {
331       Grob * closest_to_flag = extremal_heads (me)[dir];
332       Grob * dots = closest_to_flag
333         ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
334
335       if (dots)
336         {
337           Real dp = Staff_symbol_referencer::position_f  (dots);
338           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
339             / Staff_symbol_referencer::staff_space (me); 
340
341           /*
342             Very gory: add myself to the X-support of the parent,
343             which should be a dot-column.
344            */
345           if (dir * (st + flagy -  dp) < 0.5)
346             {
347               Grob *par = dots->get_parent (X_AXIS);
348
349               if (Dot_column::has_interface (par))
350                 {
351                   Side_position_interface::add_support (par, me);
352
353                   /*
354                     TODO: apply some better logic here. The flag is
355                     curved inwards, so this will typically be too
356                     much.
357                   */
358                 }
359             }
360         }
361     }
362
363
364   return st;
365 }
366
367
368
369 /*
370   
371   the log of the duration (Number of hooks on the flag minus two)
372  */
373 int
374 Stem::duration_log (Grob*me) 
375 {
376   SCM s = me->get_grob_property ("duration-log");
377   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
378 }
379
380 void
381 Stem::position_noteheads (Grob*me)
382 {
383   if (!heads_i (me))
384     return;
385   
386   Link_array<Grob> heads =
387     Pointer_group_interface__extract_grobs (me, (Grob*)0, "heads");
388
389   heads.sort (compare_position);
390   Direction dir =get_direction (me);
391   
392   if (dir < 0)
393     heads.reverse ();
394
395
396   Grob *hed = support_head (me);
397   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
398   for (int i=0; i < heads.size (); i++)
399     {
400       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
401                                 X_AXIS);
402     }
403   
404   bool parity= true;            // todo: make me settable.
405   int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
406   for (int i=1; i < heads.size (); i ++)
407     {
408       Real p = Staff_symbol_referencer::position_f (heads[i]);
409       int dy =abs (lastpos- (int)p);
410
411       if (dy <= 1)
412         {
413           if (parity)
414             {
415               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
416
417               heads[i]->translate_axis (l * get_direction (me), X_AXIS);
418             }
419           parity = !parity;
420         }
421       else
422         parity = true;
423       
424       lastpos = int (p);
425     }
426 }
427
428 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
429 SCM
430 Stem::before_line_breaking (SCM smob)
431 {
432   Grob*me = unsmob_grob (smob);
433
434   if (!invisible_b (me))
435     {
436       stem_end_position (me);   // ugh. Trigger direction calc.
437       position_noteheads (me);
438     }
439   else
440     {
441       me->remove_grob_property ("molecule-callback");
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
521           /*
522             perhaps the detection whether this correction is needed should
523             happen in a different place  to avoid the recursion.
524             
525             --hwn.
526           */
527           Grob *first = first_head(me);
528           int sz = Staff_symbol_referencer::line_count (me)-1;
529           int p = (int)rint (Staff_symbol_referencer::position_f (first));
530           staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
531         }
532       else
533         {
534           staffline_offs = "2";
535         }
536     }
537   else
538     {
539       staffline_offs = "";
540     }
541   char c = (get_direction (me) == UP) ? 'u' : 'd';
542   String index_str
543     = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
544   Molecule m
545     = Font_interface::get_default_font (me)->find_by_name (index_str);
546   if (!fstyle.empty_b ())
547     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
548   return m;
549 }
550
551 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
552 SCM
553 Stem::dim_callback (SCM e, SCM ax)
554 {
555   Axis a = (Axis) gh_scm2int (ax);
556   assert (a == X_AXIS);
557   Grob *se = unsmob_grob (e);
558   Interval r (0, 0);
559   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
560     ;   // TODO!
561   else
562     {
563       r = flag (se).extent (X_AXIS);
564     }
565   return ly_interval2scm (r);
566 }
567  
568
569
570 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
571
572 SCM
573 Stem::brew_molecule (SCM smob) 
574 {
575   Grob*me = unsmob_grob (smob);
576   Molecule mol;
577   Direction d = get_direction (me);
578   
579   
580   Real y1 = Staff_symbol_referencer::position_f (first_head (me));
581   Real y2 = stem_end_position (me);
582   
583   Interval stem_y (y1 <? y2,y2 >? y1);
584
585
586   // dy?
587   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
588
589   if (Grob *hed = support_head (me))
590     {
591       /*
592         must not take ledgers into account.
593        */
594       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
595       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
596
597       y_attach = head_height.linear_combination (y_attach);
598       stem_y[Direction (-d)] += d * y_attach/dy;
599     }
600   
601   if (!invisible_b (me))
602     {
603       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
604         // URG
605         * me->paper_l ()->get_var ("linethickness");
606       
607       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
608                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
609       mol.add_molecule (ss);
610     }
611
612   if (!beam_l (me) && abs (duration_log (me)) > 2)
613     {
614       Molecule fl = flag (me);
615       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
616       mol.add_molecule (fl);
617     }
618
619   return mol.smobbed_copy ();
620 }
621
622 /*
623   move the stem to right of the notehead if it is up.
624  */
625 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
626 SCM
627 Stem::off_callback (SCM element_smob, SCM)
628 {
629   Grob *me = unsmob_grob (element_smob);
630   
631   Real r=0;
632
633   if (invisible_b (me))
634     {
635       return gh_double2scm (0.0);
636     }
637   
638   if (Grob * f = first_head (me))
639     {
640       Interval head_wid = Note_head::head_extent(f, X_AXIS);
641
642       Real attach =
643         Note_head::stem_attachment_coordinate(f, X_AXIS);
644
645       Direction d = get_direction (me);
646
647       Real real_attach = head_wid.linear_combination (d * attach);
648
649       r = real_attach;
650
651       /*
652         If not centered: correct for stem thickness.
653        */
654       if (attach)
655         {
656           Real rule_thick
657             = gh_scm2double (me->get_grob_property ("thickness"))
658             * me->paper_l ()->get_var ("linethickness");
659
660           
661           r += - d * rule_thick * 0.5;
662         }
663     }
664   return gh_double2scm (r);
665 }
666
667
668
669 Grob*
670 Stem::beam_l (Grob*me)
671 {
672   SCM b=  me->get_grob_property ("beam");
673   return unsmob_grob (b);
674 }
675
676
677 // ugh still very long.
678 Stem_info
679 Stem::calc_stem_info (Grob*me) 
680 {
681   SCM scm_info = me->get_grob_property ("stem-info");
682
683   if (gh_pair_p (scm_info ))
684     {
685       Stem_info si ;
686
687       si.ideal_y = gh_scm2double (gh_car (scm_info)); 
688       si.max_y = gh_scm2double (gh_cadr (scm_info)); 
689       si.min_y = gh_scm2double (gh_caddr (scm_info));
690
691       return si;
692     }
693     
694   Grob * beam = beam_l (me);
695
696   Direction beam_dir = Directional_element_interface::get (beam);
697   if (!beam_dir)
698     {
699       programming_error ("Beam dir not set.");
700       beam_dir = UP;
701     }
702     
703
704   Real staff_space = Staff_symbol_referencer::staff_space (me);
705   Real half_space = staff_space / 2;
706   
707   int multiplicity = Beam::get_multiplicity (beam);
708   Real interbeam_f = Beam::get_interbeam (beam);
709
710   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
711   Stem_info info; 
712   info.ideal_y = chord_start_f (me);
713
714   // for simplicity, we calculate as if dir == UP
715   info.ideal_y *= beam_dir;
716   SCM grace_prop = me->get_grob_property ("grace");
717
718   bool grace_b = to_boolean (grace_prop);
719   
720   Array<Real> a;
721   SCM s;
722   
723   s = me->get_grob_property ("beamed-minimum-lengths");
724   a.clear ();
725   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
726     a.push (gh_scm2double (ly_car (q)));
727
728
729   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
730   s = me->get_grob_property ("beamed-lengths");
731
732   a.clear ();
733   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
734     a.push (gh_scm2double (ly_car (q)));
735
736   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
737
738   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
739     /* normal beamed stem */
740     {
741       if (multiplicity)
742         {
743           info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
744         }
745       info.min_y = info.ideal_y;
746       info.max_y = 1000;  // INT_MAX;
747
748       info.ideal_y += stem_length;
749       info.min_y += minimum_length;
750
751       /*
752         lowest beam of (UP) beam must never be lower than second staffline
753
754         Hmm, reference (Wanske?)
755
756         Although this (additional) rule is probably correct,
757         I expect that highest beam (UP) should also never be lower
758         than middle staffline, just as normal stems.
759         
760       */
761       bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
762       if (!grace_b && !no_extend_b)
763         {
764           /* highest beam of (UP) beam must never be lower than middle
765              staffline
766              lowest beam of (UP) beam must never be lower than second staffline
767            */
768           info.min_y =
769             info.min_y >? 0
770             >? (- 2 * half_space - thick
771                 + (multiplicity > 0) * thick
772                 + interbeam_f * (multiplicity - 1));
773         }
774     }
775   else
776     /* knee */
777     {
778       info.ideal_y -= thick;
779       info.max_y = info.ideal_y;
780       info.min_y = - 1000 ; // INT_MAX;
781
782       info.ideal_y -= stem_length;
783       info.max_y -= minimum_length;
784     }
785   
786   info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
787
788   s = beam->get_grob_property ("shorten");
789   if (gh_number_p (s))
790     info.ideal_y -= gh_scm2double (s);
791
792   Grob *common = me->common_refpoint (beam, Y_AXIS);
793   Real interstaff_f = beam_dir *
794     (me->relative_coordinate (common, Y_AXIS)
795      - beam->relative_coordinate (common, Y_AXIS));
796   
797   info.ideal_y += interstaff_f;
798   info.min_y += interstaff_f;
799   info.max_y += interstaff_f ;
800
801   me->set_grob_property ("stem-info",
802                          scm_list_n (gh_double2scm (info.ideal_y),
803                                      gh_double2scm (info.max_y),
804                                      gh_double2scm (info.min_y),
805                                      SCM_UNDEFINED));
806   
807   return info;
808 }
809
810 ADD_INTERFACE (Stem,"stem-interface",
811   "A stem",
812   "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");
813