]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.5.25
[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--2001 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 (0),gh_int2scm (0));
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 0;
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
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     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_element (me, ly_symbol2scm ("heads"), n);
232     }
233   else
234     {
235       n->set_grob_property ("rest", n->self_scm ());
236     }
237 }
238
239 bool
240 Stem::invisible_b (Grob*me)
241 {
242   return ! (heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
243 }
244
245 int
246 Stem::get_center_distance (Grob*me, Direction d)
247 {
248   int staff_center = 0;
249   int distance = (int) (d* (head_positions (me)[d] - staff_center));
250   return distance >? 0;
251 }
252
253 Direction
254 Stem::get_default_dir (Grob*me) 
255 {
256   int du = get_center_distance (me,UP);
257   int dd = get_center_distance (me,DOWN);
258
259   if (sign (dd - du))
260     return Direction (sign (dd -du));
261
262   return to_dir (me->get_grob_property ("neutral-direction"));
263 }
264
265 Real
266 Stem::get_default_stem_end_position (Grob*me) 
267 {
268   bool grace_b = to_boolean (me->get_grob_property ("grace"));
269   SCM s;
270   Array<Real> a;
271
272   Real length_f = 0.;
273   SCM scm_len = me->get_grob_property ("length");
274   if (gh_number_p (scm_len))
275     {
276       length_f = gh_scm2double (scm_len);
277     }
278   else
279     {
280       s = me->get_grob_property ("lengths");
281       for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
282         a.push (gh_scm2double (ly_car (q)));
283                 
284       // stem uses half-spaces
285       length_f = a[ ((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
286     }
287
288
289   a.clear ();
290   s = me->get_grob_property ("stem-shorten");
291   for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
292     a.push (gh_scm2double (ly_car (q)));
293
294
295   // stem uses half-spaces
296
297   // fixme: use scm_list_n_ref () iso. array[]
298   Real shorten_f = a[ ((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
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   /* 
311     stems in unnatural (forced) direction should be shortened, 
312     according to [Roush & Gourlay]
313    */
314   if (( (int)chord_start_f (me))
315       && (get_direction (me) != get_default_dir (me)))
316     length_f -= shorten_f;
317
318   Interval hp = head_positions (me);  
319   Real st = hp[dir] + dir * length_f;
320
321
322   
323
324   /*
325     Make a little room if we have a flag and there is a dot.
326
327     TODO:
328
329     maybe  we should consider moving the dot to the right?
330   */
331   if (!beam_l (me)
332       && flag_i (me))
333     {
334       Grob * closest_to_flag = extremal_heads (me)[dir];
335       Grob * dots = closest_to_flag
336         ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
337
338       if (dots)
339         {
340           Real dp = Staff_symbol_referencer::position_f  (dots);
341           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2; // should divide by staffspace
342
343           /*
344             Very gory: add myself to the X-support of the parent,
345             which should be a dot-column.
346            */
347           if (dir * (st + flagy -  dp) < 0.5)
348             Side_position_interface::add_support (dots->get_parent (X_AXIS), me);
349
350           /*
351             previous approach was to lengthen the stem. This is not
352             good typesetting practice.  */
353         }
354     }
355
356
357   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
358    if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
359       st = 0.0;
360
361   return st;
362 }
363
364
365
366 /*
367   Number of hooks on the flag, ie. the log of the duration.
368  */
369 int
370 Stem::flag_i (Grob*me) 
371 {
372   SCM s = me->get_grob_property ("duration-log");
373   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
374 }
375
376 void
377 Stem::position_noteheads (Grob*me)
378 {
379   if (!heads_i (me))
380     return;
381   
382   Link_array<Grob> heads =
383     Pointer_group_interface__extract_elements (me, (Grob*)0, "heads");
384
385   heads.sort (compare_position);
386   Direction dir =get_direction (me);
387   
388   if (dir < 0)
389     heads.reverse ();
390
391
392   Grob *hed = support_head (me);
393   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
394   for (int i=0; i < heads.size (); i++)
395     {
396       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
397                                 X_AXIS);
398     }
399   
400   bool parity= true;            // todo: make me settable.
401   int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
402   for (int i=1; i < heads.size (); i ++)
403     {
404       Real p = Staff_symbol_referencer::position_f (heads[i]);
405       int dy =abs (lastpos- (int)p);
406
407       if (dy <= 1)
408         {
409           if (parity)
410             {
411               Real l = heads[i]->extent (heads[i], X_AXIS).length ();
412               heads[i]->translate_axis (l * get_direction (me), X_AXIS);
413             }
414           parity = !parity;
415         }
416       else
417         parity = true;
418       
419       lastpos = int (p);
420     }
421 }
422
423 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
424 SCM
425 Stem::before_line_breaking (SCM smob)
426 {
427   Grob*me = unsmob_grob (smob);
428   stem_end_position (me);       // ugh. Trigger direction calc.
429   position_noteheads (me);
430
431   if (invisible_b (me))
432     {
433       me->remove_grob_property ("molecule-callback");
434       // suicide ();
435     }
436   
437   set_spacing_hints (me);
438   return SCM_UNSPECIFIED;
439 }
440
441
442 /*
443   ugh.
444   When in a beam with tuplet brackets, brew_mol is called early,
445   caching a wrong value.
446  */
447 MAKE_SCHEME_CALLBACK (Stem, height, 2);
448 SCM
449 Stem::height (SCM smob, SCM ax)
450 {
451   Axis a = (Axis)gh_scm2int (ax);
452   Grob * me = unsmob_grob (smob);
453   assert (a == Y_AXIS);
454
455   SCM mol = me->get_uncached_molecule ();
456   Interval iv;
457   if (mol != SCM_EOL)
458     iv = unsmob_molecule (mol)->extent (a);
459   return ly_interval2scm (iv);
460 }
461
462
463 /**
464    set stem directions for hinting the optical spacing correction.
465
466    Modifies DIR_LIST property of the Stem's Paper_column
467
468    TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
469  */
470 void
471 Stem::set_spacing_hints (Grob*me) 
472 {
473   if (!invisible_b (me))
474     {
475       SCM scmdir  = gh_int2scm (get_direction (me));
476
477       Item* item = dynamic_cast<Item*> (me);
478       Item * col =  item->column_l ();
479       SCM dirlist =col->get_grob_property ("dir-list");
480       if (scm_c_memq (scmdir, dirlist) == SCM_BOOL_F)
481         {
482           dirlist = gh_cons (scmdir, dirlist);
483           col->set_grob_property ("dir-list", dirlist);
484         }
485     }
486 }
487
488 Molecule
489 Stem::flag (Grob*me)
490 {
491   /* TODO: rename flag-style into something more appropriate,
492    e.g. "stroke-style", maybe with values "" (i.e. no stroke),
493    "single" and "double".  Needs more discussion.
494   */
495   String style, fstyle, staffline_offs;
496   SCM fst = me->get_grob_property ("flag-style");
497   if (gh_string_p (fst))
498     {
499       fstyle = ly_scm2string (fst);
500     }
501
502   SCM st = me->get_grob_property ("style");
503   if (gh_symbol_p (st))
504     {
505       style = (ly_scm2string (scm_symbol_to_string (st)));
506     }
507   else
508     {
509       style = "";
510     }
511   if (String::compare_i (style, "mensural") == 0)
512     /* Mensural notation: For notes on staff lines, use different
513        flags than for notes between staff lines.  The idea is that
514        flags are always vertically aligned with the staff lines,
515        regardless if the note head is on a staff line or between two
516        staff lines.  In other words, the inner end of a flag always
517        touches a staff line.
518     */
519     {
520       /* Urrgh!  We have to detect wether this stem ends on a staff
521          line or between two staff lines.  But we can not call
522          stem_end_position(me) or get_default_stem_end_position(me),
523          since this encounters the flag and hence results in an
524          infinite recursion.  However, in pure mensural notation,
525          there are no multiple note heads attached to a single stem,
526          neither is there usually need for using the stem_shorten
527          property (except for 32th and 64th notes, but that is not a
528          problem since the stem length in this case is augmented by
529          an integral multiple of staff_space).  Hence, it should be
530          sufficient to just take the first note head, assume it's
531          the only one, look if it's on a staff line, and select the
532          flag's shape accordingly.  In the worst case, the shape
533          looks slightly misplaced, but that will usually be the
534          programmer's fault (e.g. when trying to attach multiple
535          note heads to a single stem in mensural notation).
536
537       */
538         Grob *first = first_head(me);
539         int sz = Staff_symbol_referencer::line_count (me)-1;
540         int p = (int)rint (Staff_symbol_referencer::position_f (first));
541         staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
542     }
543   else
544     {
545         staffline_offs = "";
546     }
547   char c = (get_direction (me) == UP) ? 'u' : 'd';
548   String index_str
549     = String ("flags-") + style + to_str (c) + staffline_offs + to_str (flag_i (me));
550   Molecule m
551     = Font_interface::get_default_font (me)->find_by_name (index_str);
552   if (!fstyle.empty_b ())
553     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
554   return m;
555 }
556
557 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
558 SCM
559 Stem::dim_callback (SCM e, SCM ax)
560 {
561   Axis a = (Axis) gh_scm2int (ax);
562   assert (a == X_AXIS);
563   Grob *se = unsmob_grob (e);
564   Interval r (0, 0);
565   if (unsmob_grob (se->get_grob_property ("beam")) || abs (flag_i (se)) <= 2)
566     ;   // TODO!
567   else
568     {
569       r = flag (se).extent (X_AXIS);
570     }
571   return ly_interval2scm (r);
572 }
573  
574
575
576 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
577
578 SCM
579 Stem::brew_molecule (SCM smob) 
580 {
581   Grob*me = unsmob_grob (smob);
582   Molecule mol;
583   Direction d = get_direction (me);
584   
585   
586   Real y1 = Staff_symbol_referencer::position_f (first_head (me));
587   Real y2 = stem_end_position (me);
588   
589   Interval stem_y (y1 <? y2,y2 >? y1);
590
591
592   // dy?
593   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
594     
595   if (Grob *hed = support_head (me))
596     {
597       /*
598         must not take ledgers into account.
599        */
600       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
601       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
602
603       y_attach = head_height.linear_combination (y_attach);
604       stem_y[Direction (-d)] += d * 2*y_attach;
605     }
606   
607   if (!invisible_b (me))
608     {
609       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
610         // URG
611         * me->paper_l ()->get_var ("stafflinethickness");
612       
613       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
614                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
615       mol.add_molecule (ss);
616     }
617
618   if (!beam_l (me) && abs (flag_i (me)) > 2)
619     {
620       Molecule fl = flag (me);
621       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
622       mol.add_molecule (fl);
623     }
624
625   return mol.smobbed_copy ();
626 }
627
628 /*
629   move the stem to right of the notehead if it is up.
630  */
631 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
632 SCM
633 Stem::off_callback (SCM element_smob, SCM)
634 {
635   Grob *me = unsmob_grob (element_smob);
636   
637   Real r=0;
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 ("stafflinethickness");
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   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   int multiplicity = Beam::get_multiplicity (beam);
694
695
696   SCM space_proc = beam->get_grob_property ("space-function");
697   SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
698   Real interbeam_f = gh_scm2double (space) * staff_space;
699
700   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
701   Stem_info info; 
702   info.idealy_f_ = chord_start_f (me);
703
704   // for simplicity, we calculate as if dir == UP
705   info.idealy_f_ *= beam_dir;
706   SCM grace_prop = me->get_grob_property ("grace");
707
708   bool grace_b = to_boolean (grace_prop);
709   
710   Array<Real> a;
711   SCM s;
712   
713   s = me->get_grob_property ("beamed-minimum-lengths");
714   a.clear ();
715   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
716     a.push (gh_scm2double (ly_car (q)));
717
718
719   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
720   s = me->get_grob_property ("beamed-lengths");
721
722   a.clear ();
723   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
724     a.push (gh_scm2double (ly_car (q)));
725
726   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
727
728   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
729     /* normal beamed stem */
730     {
731       if (multiplicity)
732         {
733           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
734         }
735       info.miny_f_ = info.idealy_f_;
736       info.maxy_f_ = INT_MAX;
737
738       info.idealy_f_ += stem_length;
739       info.miny_f_ += minimum_length;
740
741       /*
742         lowest beam of (UP) beam must never be lower than second staffline
743
744         Hmm, reference (Wanske?)
745
746         Although this (additional) rule is probably correct,
747         I expect that highest beam (UP) should also never be lower
748         than middle staffline, just as normal stems.
749         
750       */
751       bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
752       if (!grace_b && !no_extend_b)
753         {
754           /* highest beam of (UP) beam must never be lower than middle
755              staffline
756              lowest beam of (UP) beam must never be lower than second staffline
757            */
758           info.miny_f_ =
759             info.miny_f_ >? 0
760             >? (- 2 * half_space - thick
761                 + (multiplicity > 0) * thick
762                 + interbeam_f * (multiplicity - 1));
763         }
764     }
765   else
766     /* knee */
767     {
768       info.idealy_f_ -= thick;
769       info.maxy_f_ = info.idealy_f_;
770       info.miny_f_ = -INT_MAX;
771
772       info.idealy_f_ -= stem_length;
773       info.maxy_f_ -= minimum_length;
774     }
775   
776   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
777
778   s = beam->get_grob_property ("shorten");
779   if (gh_number_p (s))
780     info.idealy_f_ -= gh_scm2double (s);
781
782  Grob *common = me->common_refpoint (beam, Y_AXIS);
783   Real interstaff_f = beam_dir *
784  (me->relative_coordinate (common, Y_AXIS)
785      - beam->relative_coordinate (common, Y_AXIS));
786
787   info.idealy_f_ += interstaff_f;
788   info.miny_f_ += interstaff_f;
789   info.maxy_f_ += interstaff_f ;
790
791   return info;
792 }
793
794 bool
795 Stem::has_interface (Grob*m)
796 {
797   return m && m->has_interface (ly_symbol2scm ("stem-interface"));
798 }
799
800 void
801 Stem::set_interface (Grob*me)
802 {    
803   me->set_interface (ly_symbol2scm ("stem-interface"));
804 }