]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
bugfix: don't return flag for invisible stems.
[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--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10
11   TODO: fix naming.
12
13   Stem-end, chord-start, etc. is all confusing naming.
14 */
15
16 #include "stem.hh"
17 #include "spanner.hh"
18
19 #include <cmath>                // rint
20 using namespace std;
21
22 #include "beam.hh"
23 #include "directional-element-interface.hh"
24 #include "dot-column.hh"
25 #include "font-interface.hh"
26 #include "international.hh"
27 #include "lookup.hh"
28 #include "misc.hh"
29 #include "note-head.hh"
30 #include "output-def.hh"
31 #include "paper-column.hh"
32 #include "pointer-group-interface.hh"
33 #include "rest.hh"
34 #include "rhythmic-head.hh"
35 #include "side-position-interface.hh"
36 #include "staff-symbol-referencer.hh"
37 #include "stem-tremolo.hh"
38 #include "warn.hh"
39
40 void
41 Stem::set_beaming (Grob *me, int beam_count, Direction d)
42 {
43   SCM pair = me->get_property ("beaming");
44
45   if (!scm_is_pair (pair))
46     {
47       pair = scm_cons (SCM_EOL, SCM_EOL);
48       me->set_property ("beaming", pair);
49     }
50
51   SCM lst = index_get_cell (pair, d);
52   if (beam_count)
53     for (int i = 0; i < beam_count; i++)
54       lst = scm_cons (scm_from_int (i), lst);
55   else
56     lst = SCM_BOOL_F;
57   
58   index_set_cell (pair, d, lst);
59 }
60
61 int
62 Stem::get_beaming (Grob *me, Direction d)
63 {
64   SCM pair = me->get_property ("beaming");
65   if (!scm_is_pair (pair))
66     return 0;
67
68   SCM lst = index_get_cell (pair, d);
69
70   int len = scm_ilength (lst);
71   return max (len, 0);
72 }
73
74 Interval
75 Stem::head_positions (Grob *me)
76 {
77   if (head_count (me))
78     {
79       Drul_array<Grob *> e (extremal_heads (me));
80       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
81                        Staff_symbol_referencer::get_position (e[UP]));
82     }
83   return Interval ();
84 }
85
86 Real
87 Stem::chord_start_y (Grob *me)
88 {
89   Interval hp = head_positions (me);
90   if (!hp.is_empty ())
91     return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
92       * 0.5;
93   return 0;
94 }
95
96
97
98 void
99 Stem::set_stemend (Grob *me, Real se)
100 {
101   // todo: margins
102   Direction d = get_grob_direction (me);
103
104   if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
105     me->warning (_ ("weird stem size, check for narrow beams"));
106
107   me->set_property ("stem-end-position", scm_from_double (se));
108 }
109
110 /* Note head that determines hshift for upstems
111    WARNING: triggers direction  */
112 Grob *
113 Stem::support_head (Grob *me)
114 {
115   extract_grob_set (me, "note-heads", heads);
116   if (heads.size () == 1)
117     return heads[0];
118
119   return first_head (me);
120 }
121
122 int
123 Stem::head_count (Grob *me)
124 {
125   return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
126 }
127
128 /* The note head which forms one end of the stem.
129    WARNING: triggers direction  */
130 Grob *
131 Stem::first_head (Grob *me)
132 {
133   Direction d = get_grob_direction (me);
134   if (d)
135     return extremal_heads (me)[-d];
136   return 0;
137 }
138
139 /* The note head opposite to the first head.  */
140 Grob *
141 Stem::last_head (Grob *me)
142 {
143   Direction d = get_grob_direction (me);
144   if (d)
145     return extremal_heads (me)[d];
146   return 0;
147 }
148
149 /*
150   START is part where stem reaches `last' head.
151
152   This function returns a drul with (bottom-head, top-head).
153 */
154 Drul_array<Grob *>
155 Stem::extremal_heads (Grob *me)
156 {
157   const int inf = INT_MAX;
158   Drul_array<int> extpos;
159   extpos[DOWN] = inf;
160   extpos[UP] = -inf;
161
162   Drul_array<Grob *> exthead (0, 0);
163   extract_grob_set (me, "note-heads", heads);
164
165   for (vsize i = heads.size (); i--;)
166     {
167       Grob *n = heads[i];
168       int p = Staff_symbol_referencer::get_rounded_position (n);
169
170       Direction d = LEFT;
171       do
172         {
173           if (d * p > d * extpos[d])
174             {
175               exthead[d] = n;
176               extpos[d] = p;
177             }
178         }
179       while (flip (&d) != DOWN);
180     }
181   return exthead;
182 }
183
184 /* The positions, in ascending order.  */
185 vector<int>
186 Stem::note_head_positions (Grob *me)
187 {
188   vector<int> ps;
189   extract_grob_set (me, "note-heads", heads);
190
191   for (vsize i = heads.size (); i--;)
192     {
193       Grob *n = heads[i];
194       int p = Staff_symbol_referencer::get_rounded_position (n);
195
196       ps.push_back (p);
197     }
198
199   vector_sort (ps, less<int> ());
200   return ps;
201 }
202
203 void
204 Stem::add_head (Grob *me, Grob *n)
205 {
206   n->set_object ("stem", me->self_scm ());
207
208   if (Note_head::has_interface (n))
209     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
210   else if (Rest::has_interface (n))
211     Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
212 }
213
214 bool
215 Stem::is_invisible (Grob *me)
216 {
217   return !is_normal_stem (me)
218     && (robust_scm2double (me->get_property ("stemlet-length"),
219                            0.0) == 0.0);
220 }
221
222
223 bool
224 Stem::is_normal_stem (Grob *me)
225 {
226   return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
227 }
228
229
230 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
231 SCM
232 Stem::pure_height (SCM smob, SCM start, SCM end)
233 {
234   (void) start;
235   (void) end;
236
237   Grob *me = unsmob_grob (smob);
238   Interval iv;
239
240   if (!is_normal_stem (me))
241     return ly_interval2scm (iv);
242
243   /* if we are part of a cross-staff beam, return empty */
244   if (get_beam (me) && Beam::is_cross_staff (get_beam (me)))
245     return ly_interval2scm (iv);
246   
247   Real ss = Staff_symbol_referencer::staff_space (me);
248   Real len = scm_to_double (calc_length (smob)) * ss / 2;
249   Direction dir = get_grob_direction (me);
250
251   Interval hp = head_positions (me);
252   if (dir == UP)
253     iv = Interval (0, len);
254   else
255     iv = Interval (-len, 0);
256
257   if (!hp.is_empty ())
258     iv.translate (hp[dir] * ss / 2);
259
260   return ly_interval2scm (iv);
261 }
262
263 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
264 SCM
265 Stem::calc_stem_end_position (SCM smob)
266 {
267   Grob *me = unsmob_grob (smob);
268
269   if (!head_count (me))
270     return scm_from_double (0.0);
271
272   if (Grob *beam = get_beam (me))
273     {
274       (void) beam->get_property ("quantized-positions");
275       return me->get_property ("stem-end-position");
276     }
277   
278   vector<Real> a;
279
280   /* WARNING: IN HALF SPACES */
281   Real length = robust_scm2double (me->get_property ("length"), 7);
282
283   Direction dir = get_grob_direction (me);
284   Interval hp = head_positions (me);
285   Real stem_end = dir ? hp[dir] + dir * length : 0;
286
287   /* TODO: change name  to extend-stems to staff/center/'()  */
288   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
289   if (!no_extend_b && dir * stem_end < 0)
290     stem_end = 0.0;
291
292   return scm_from_double (stem_end);
293 }
294
295 /* Length is in half-spaces (or: positions) here. */
296 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
297 SCM
298 Stem::calc_length (SCM smob)
299 {
300   Grob *me = unsmob_grob (smob);
301   
302   SCM details = me->get_property ("details");
303   int durlog = duration_log (me);
304
305   Real ss = Staff_symbol_referencer::staff_space (me);
306   Real length = 7;
307   SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
308   if (scm_is_pair (s))
309     length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
310
311   Direction dir = get_grob_direction (me);
312
313   /* Stems in unnatural (forced) direction should be shortened,
314      according to [Roush & Gourlay] */
315   Interval hp = head_positions (me);
316   if (dir && dir * hp[dir] >= 0)
317     {
318       SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
319       SCM scm_shorten = scm_is_pair (sshorten)
320         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
321       Real shorten = 2* robust_scm2double (scm_shorten, 0);
322
323       /* On boundary: shorten only half */
324       if (abs (head_positions (me)[dir]) <= 1)
325         shorten *= 0.5;
326
327       length -= shorten;
328     }
329
330   length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
331
332   /* Tremolo stuff.  */
333   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
334   if (t_flag && !unsmob_grob (me->get_object ("beam")))
335     {
336       /* Crude hack: add extra space if tremolo flag is there.
337
338       We can't do this for the beam, since we get into a loop
339       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
340
341       Real minlen = 1.0
342         + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
343
344       /* We don't want to add the whole extent of the flag because the trem
345          and the flag can overlap partly. beam_translation gives a good
346          approximation */
347       if (durlog >= 3)
348         {
349           Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
350           /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
351           minlen += 2 * (durlog - 1.5) * beam_trans;
352
353           /* up-stems need even a little more space to avoid collisions. This
354              needs to be in sync with the tremolo positioning code in
355              Stem_tremolo::print */
356           if (dir == UP)
357             minlen += beam_trans;
358         }
359       length = max (length, minlen + 1.0);
360     }
361   
362   return scm_from_double (length);
363 }
364 /* The log of the duration (Number of hooks on the flag minus two)  */
365 int
366 Stem::duration_log (Grob *me)
367 {
368   SCM s = me->get_property ("duration-log");
369   return (scm_is_number (s)) ? scm_to_int (s) : 2;
370 }
371
372 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
373 SCM
374 Stem::calc_positioning_done (SCM smob)
375 {
376   Grob *me = unsmob_grob (smob);  
377   if (!head_count (me))
378     return SCM_BOOL_T;
379
380   me->set_property ("positioning-done", SCM_BOOL_T);
381   
382   extract_grob_set (me, "note-heads", ro_heads);
383   vector<Grob*> heads (ro_heads);
384   vector_sort (heads, position_less);
385   Direction dir = get_grob_direction (me);
386
387   if (dir < 0)
388     reverse (heads);
389
390   Real thick = thickness (me);
391
392   Grob *hed = support_head (me);
393   if (!dir)
394     {
395       programming_error ("Stem dir must be up or down.");
396       dir = UP;
397       set_grob_direction (me, dir);
398     }
399
400   bool is_harmonic_centered = false;
401   for (vsize i = 0; i < heads.size (); i++)
402     is_harmonic_centered = is_harmonic_centered 
403       || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
404   is_harmonic_centered = is_harmonic_centered && is_invisible (me);
405
406   Real w = hed->extent (hed, X_AXIS)[dir];
407   for (vsize i = 0; i < heads.size (); i++)
408     {
409       Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
410
411       if (is_harmonic_centered)
412         amount =
413           hed->extent (hed, X_AXIS).linear_combination (CENTER)
414           - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
415       
416       heads[i]->translate_axis (amount, X_AXIS);
417     }
418   bool parity = true;
419   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
420   for (vsize i = 1; i < heads.size (); i++)
421     {
422       Real p = Staff_symbol_referencer::get_position (heads[i]);
423       Real dy = fabs (lastpos- p);
424
425       /*
426         dy should always be 0.5, 0.0, 1.0, but provide safety margin
427         for rounding errors.
428       */
429       if (dy < 1.1)
430         {
431           if (parity)
432             {
433               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
434
435               Direction d = get_grob_direction (me);
436               /*
437                 Reversed head should be shifted ell-thickness, but this
438                 looks too crowded, so we only shift ell-0.5*thickness.
439
440                 This leads to assymetry: Normal heads overlap the
441                 stem 100% whereas reversed heads only overlaps the
442                 stem 50%
443               */
444
445               Real reverse_overlap = 0.5;
446               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
447                                         X_AXIS);
448
449               if (is_invisible (me))
450                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
451                                           X_AXIS);
452
453               /* TODO:
454
455               For some cases we should kern some more: when the
456               distance between the next or prev note is too large, we'd
457               get large white gaps, eg.
458
459               |
460               X|
461               |X  <- kern this.
462               |
463               X
464
465               */
466             }
467           parity = !parity;
468         }
469       else
470         parity = true;
471
472       lastpos = int (p);
473     }
474
475   return SCM_BOOL_T;
476 }
477
478 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
479 SCM
480 Stem::calc_direction (SCM smob)
481 {
482   Grob *me = unsmob_grob (smob);
483   Direction dir = CENTER;
484   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
485     {
486       SCM ignore_me = beam->get_property ("direction");
487       (void) ignore_me;
488       dir = get_grob_direction (me);
489     }
490   else
491     {
492       SCM dd = me->get_property ("default-direction");
493       dir = to_dir (dd);
494       if (!dir)
495         return me->get_property ("neutral-direction");
496     }
497   
498   return scm_from_int (dir);
499 }
500
501 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
502 SCM
503 Stem::calc_default_direction (SCM smob)
504 {
505   Grob *me = unsmob_grob (smob);
506
507   Direction dir = CENTER;
508   int staff_center = 0;
509   Interval hp = head_positions (me);
510   if (!hp.is_empty ())
511     {
512       int udistance = (int) (UP * hp[UP] - staff_center);
513       int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
514       
515       dir = Direction (sign (ddistance - udistance));
516     }
517   
518   return scm_from_int (dir);
519 }
520
521
522 MAKE_SCHEME_CALLBACK (Stem, height, 1);
523 SCM
524 Stem::height (SCM smob)
525 {
526   Grob *me = unsmob_grob (smob);
527   if (!is_normal_stem (me))
528     return ly_interval2scm (Interval ());
529   
530   Direction dir = get_grob_direction (me);
531   
532   Grob *beam = get_beam (me);
533   if (beam)
534     {
535       /* trigger set-stem-lengths. */
536       beam->get_property ("quantized-positions");
537     }
538
539   /*
540     Can't get_stencil (), since that would cache stencils too early.
541     This causes problems with beams.
542    */
543   Stencil *stencil = unsmob_stencil (print (smob));
544   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
545   if (beam)
546     {
547       if (dir == CENTER)
548         {
549           programming_error ("no stem direction");
550           dir = UP;
551         }
552       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
553     }
554
555   return ly_interval2scm (iv);
556 }
557
558 Real
559 Stem::stem_end_position (Grob *me)
560 {
561   return robust_scm2double (me->get_property ("stem-end-position"), 0);
562 }
563
564 Stencil
565 Stem::flag (Grob *me)
566 {
567   int log = duration_log (me);
568   if (log < 3
569       || unsmob_grob (me->get_object ("beam")))
570     return Stencil ();
571
572   if (!is_normal_stem (me))
573     return Stencil ();
574   
575   /*
576     TODO: maybe property stroke-style should take different values,
577     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
578     '() or "grace").  */
579   string flag_style;
580
581   SCM flag_style_scm = me->get_property ("flag-style");
582   if (scm_is_symbol (flag_style_scm))
583     flag_style = ly_symbol2string (flag_style_scm);
584
585   if (flag_style == "no-flag")
586     return Stencil ();
587
588   bool adjust = true;
589
590   string staffline_offs;
591   if (flag_style == "mensural")
592     /* Mensural notation: For notes on staff lines, use different
593        flags than for notes between staff lines.  The idea is that
594        flags are always vertically aligned with the staff lines,
595        regardless if the note head is on a staff line or between two
596        staff lines.  In other words, the inner end of a flag always
597        touches a staff line.
598     */
599     {
600       if (adjust)
601         {
602           int p = (int) (rint (stem_end_position (me)));
603           staffline_offs
604             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
605         }
606       else
607         staffline_offs = "2";
608     }
609   else
610     staffline_offs = "";
611
612   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
613   string font_char = flag_style
614     + to_string (dir) + staffline_offs + to_string (log);
615   Font_metric *fm = Font_interface::get_default_font (me);
616   Stencil flag = fm->find_by_name ("flags." + font_char);
617   if (flag.is_empty ())
618     me->warning (_f ("flag `%s' not found", font_char));
619
620   SCM stroke_style_scm = me->get_property ("stroke-style");
621   if (scm_is_string (stroke_style_scm))
622     {
623       string stroke_style = ly_scm2string (stroke_style_scm);
624       if (!stroke_style.empty ())
625         {
626           string font_char = to_string (dir) + stroke_style;
627           Stencil stroke = fm->find_by_name ("flags." + font_char);
628           if (stroke.is_empty ())
629             me->warning (_f ("flag stroke `%s' not found", font_char));
630           else
631             flag.add_stencil (stroke);
632         }
633     }
634
635   return flag;
636 }
637
638 MAKE_SCHEME_CALLBACK (Stem, width, 1);
639 SCM
640 Stem::width (SCM e)
641 {
642   Grob *me = unsmob_grob (e);
643
644   Interval r;
645
646   if (is_invisible (me))
647     r.set_empty ();
648   else if (unsmob_grob (me->get_object ("beam"))
649            || abs (duration_log (me)) <= 2)
650     {
651       r = Interval (-1, 1);
652       r *= thickness (me) / 2;
653     }
654   else
655     {
656       r = Interval (-1, 1) * thickness (me) * 0.5;
657       r.unite (flag (me).extent (X_AXIS));
658     }
659   return ly_interval2scm (r);
660 }
661
662 Real
663 Stem::thickness (Grob *me)
664 {
665   return scm_to_double (me->get_property ("thickness"))
666     * Staff_symbol_referencer::line_thickness (me);
667 }
668
669 MAKE_SCHEME_CALLBACK (Stem, print, 1);
670 SCM
671 Stem::print (SCM smob)
672 {
673   Grob *me = unsmob_grob (smob);
674   Grob *beam = get_beam (me);
675     
676   Stencil mol;
677   Direction d = get_grob_direction (me);
678
679   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
680                                            0.0);
681   bool stemlet = stemlet_length > 0.0;
682
683   /* TODO: make the stem start a direction ?
684      This is required to avoid stems passing in tablature chords.  */
685   Grob *lh
686     = to_boolean (me->get_property ("avoid-note-head"))
687     ? last_head (me)
688     : first_head (me);
689
690   if (!lh && !stemlet)
691     return SCM_EOL;
692
693   if (!lh && stemlet && !beam)
694     return SCM_EOL;
695
696   if (is_invisible (me))
697     return SCM_EOL;
698
699   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
700   Real y1 = y2;
701   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
702
703   if (lh)
704     y2 = Staff_symbol_referencer::get_position (lh);
705   else if (stemlet)
706     {
707       Real beam_translation = Beam::get_beam_translation (beam);
708       Real beam_thickness = Beam::get_thickness (beam);
709       int beam_count = beam_multiplicity (me).length () + 1;
710
711       y2 -= d
712         * (0.5 * beam_thickness
713            + beam_translation * max (0, (beam_count - 1))
714            + stemlet_length) / half_space;
715     }
716
717   Interval stem_y (min (y1, y2), max (y2, y1));
718
719   if (Grob *head = support_head (me))
720     {
721       /*
722         must not take ledgers into account.
723       */
724       Interval head_height = head->extent (head, Y_AXIS);
725       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
726
727       y_attach = head_height.linear_combination (y_attach);
728       stem_y[Direction (-d)] += d * y_attach / half_space;
729     }
730
731   // URG
732   Real stem_width = thickness (me);
733   Real blot
734     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
735
736   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
737                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
738
739   Stencil ss = Lookup::round_filled_box (b, blot);
740   mol.add_stencil (ss);
741
742   mol.add_stencil (get_translated_flag (me));
743
744   return mol.smobbed_copy ();
745 }
746
747 Stencil
748 Stem::get_translated_flag (Grob *me)
749 {
750   Stencil fl = flag (me);
751   if (!fl.is_empty ())
752     {
753       Direction d = get_grob_direction (me);
754       Real blot
755         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
756       Real stem_width = thickness (me);
757       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
758       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
759       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
760       fl.translate_axis (stem_width / 2, X_AXIS);
761     }
762   return fl;
763 }
764
765
766 /*
767   move the stem to right of the notehead if it is up.
768 */
769 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
770 SCM
771 Stem::offset_callback (SCM smob)
772 {
773   Grob *me = unsmob_grob (smob);
774
775   extract_grob_set (me, "rests", rests);
776   if (rests.size ())
777     {
778       Grob *rest = rests.back ();
779       Real r = rest->extent (rest, X_AXIS).center ();
780       return scm_from_double (r);
781     }
782
783   
784   if (Grob *f = first_head (me))
785     {
786       Interval head_wid = f->extent (f, X_AXIS);
787       Real attach = 0.0;
788
789       if (is_invisible (me))
790         attach = 0.0;
791       else
792         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
793
794       Direction d = get_grob_direction (me);
795       Real real_attach = head_wid.linear_combination (d * attach);
796       Real r = real_attach;
797
798       /* If not centered: correct for stem thickness.  */
799       if (attach)
800         {
801           Real rule_thick = thickness (me);
802           r += -d * rule_thick * 0.5;
803         }
804       return scm_from_double (r);
805     }
806
807   programming_error ("Weird stem.");
808   return scm_from_double (0.0);
809 }
810
811 Spanner *
812 Stem::get_beam (Grob *me)
813 {
814   SCM b = me->get_object ("beam");
815   return dynamic_cast<Spanner *> (unsmob_grob (b));
816 }
817
818 Stem_info
819 Stem::get_stem_info (Grob *me)
820 {
821   Stem_info si;
822   si.dir_ = get_grob_direction (me);
823   
824   SCM scm_info = me->get_property ("stem-info");
825   si.ideal_y_ = scm_to_double (scm_car (scm_info));
826   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
827   return si;
828 }
829
830 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
831 SCM
832 Stem::calc_stem_info (SCM smob)
833 {
834   Grob *me = unsmob_grob (smob);
835   Direction my_dir = get_grob_direction (me);
836
837   if (!my_dir)
838     {
839       programming_error ("no stem dir set");
840       my_dir = UP;
841     }
842
843   Real staff_space = Staff_symbol_referencer::staff_space (me);
844   Grob *beam = get_beam (me);
845
846   if (beam)
847     {
848       (void) beam->get_property ("beaming");
849     }
850   
851   Real beam_translation = Beam::get_beam_translation (beam);
852   Real beam_thickness = Beam::get_thickness (beam);
853   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
854   Real length_fraction
855     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
856
857   /* Simple standard stem length */
858   SCM details = me->get_property ("details");
859   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
860   
861   Real ideal_length
862     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
863     * staff_space
864     * length_fraction
865     
866     /* stem only extends to center of beam
867      */
868     - 0.5 * beam_thickness;
869
870   /* Condition: sane minimum free stem length (chord to beams) */
871   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
872
873   Real ideal_minimum_free
874     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
875     * staff_space
876     * length_fraction;
877
878   Real height_of_my_trem = 0.0;
879   Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
880   if (trem)
881     {
882       height_of_my_trem
883         = Stem_tremolo::vertical_length (trem)
884         /* hack a bit of space around the trem. */
885         + beam_translation;
886     }
887
888   
889   /* UGH
890      It seems that also for ideal minimum length, we must use
891      the maximum beam count (for this direction):
892
893      \score{ \notes\relative c''{ [a8 a32] }}
894
895      must be horizontal. */
896   Real height_of_my_beams = beam_thickness
897     + (beam_count - 1) * beam_translation;
898
899   Real ideal_minimum_length = ideal_minimum_free
900     + height_of_my_beams
901     + height_of_my_trem
902     /* stem only extends to center of beam */
903     - 0.5 * beam_thickness;
904
905   ideal_length = max (ideal_length, ideal_minimum_length);
906
907   /* Convert to Y position, calculate for dir == UP */
908   Real note_start
909     =     /* staff positions */
910     head_positions (me)[my_dir] * 0.5
911     * my_dir * staff_space;
912   Real ideal_y = note_start + ideal_length;
913
914   /* Conditions for Y position */
915
916   /* Lowest beam of (UP) beam must never be lower than second staffline
917
918   Reference?
919
920   Although this (additional) rule is probably correct,
921   I expect that highest beam (UP) should also never be lower
922   than middle staffline, just as normal stems.
923
924   Reference?
925
926   Obviously not for grace beams.
927
928   Also, not for knees.  Seems to be a good thing. */
929   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
930   bool is_knee = to_boolean (beam->get_property ("knee"));
931   if (!no_extend_b && !is_knee)
932     {
933       /* Highest beam of (UP) beam must never be lower than middle
934          staffline */
935       ideal_y = max (ideal_y, 0.0);
936       /* Lowest beam of (UP) beam must never be lower than second staffline */
937       ideal_y = max (ideal_y, (-staff_space
938                                - beam_thickness + height_of_my_beams));
939     }
940
941   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
942
943   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
944                                  details));
945   
946   Real minimum_free
947     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
948     * staff_space
949     * length_fraction;
950
951   Real minimum_length = max (minimum_free, height_of_my_trem)
952     + height_of_my_beams
953     /* stem only extends to center of beam */
954     - 0.5 * beam_thickness;
955
956   ideal_y *= my_dir;
957   Real minimum_y = note_start + minimum_length;
958   Real shortest_y = minimum_y * my_dir;
959
960   return scm_list_2 (scm_from_double (ideal_y),
961                      scm_from_double (shortest_y));
962 }
963
964 Slice
965 Stem::beam_multiplicity (Grob *stem)
966 {
967   SCM beaming = stem->get_property ("beaming");
968   Slice le = int_list_to_slice (scm_car (beaming));
969   Slice ri = int_list_to_slice (scm_cdr (beaming));
970   le.unite (ri);
971   return le;
972 }
973
974 bool
975 Stem::is_cross_staff (Grob *stem)
976 {
977   Grob *beam = unsmob_grob (stem->get_object ("beam"));
978   return beam && Beam::is_cross_staff (beam);
979 }
980
981 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
982 SCM
983 Stem::calc_cross_staff (SCM smob)
984 {
985   return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
986 }
987
988 /* FIXME:  Too many properties  */
989 ADD_INTERFACE (Stem,
990                "The stem represent the graphical stem.  "
991                "In addition, it internally connects note heads, beams and"
992                "tremolos. "
993                "Rests and whole notes have invisible stems."
994
995                "\n\nThe following properties may be set in the details list." 
996                "@table @code\n"
997                "@item  beamed-lengths \n"
998                "list of stem lengths given beam multiplicity. \n"
999                "@item beamed-minimum-free-lengths \n"
1000                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1001                "@item beamed-extreme-minimum-free-lengths\n"
1002                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1003                "@item lengths\n"
1004                "Default stem lengths. The list gives a length for each flag-count.\n"
1005                "@item stem-shorten\n"
1006                "How much a stem in a forced direction "
1007                "should be shortened. The list gives an amount depending on the number "
1008                "of flags/beams."
1009                "@end table\n"
1010                ,
1011
1012                /* properties */
1013                "avoid-note-head "
1014                "beam "
1015                "beaming "
1016                "default-direction "
1017                "details "
1018                "direction "
1019                "duration-log "
1020                "flag-style "
1021                "french-beaming "
1022                "length "
1023                "length-fraction "
1024                "max-beam-connect "
1025                "neutral-direction "
1026                "no-stem-extend "
1027                "note-heads "
1028                "positioning-done "
1029                "rests "
1030                "stem-end-position "
1031                "stem-info "
1032                "stemlet-length "
1033                "stroke-style "
1034                "thickness "
1035                "tremolo-flag "
1036                );
1037
1038 /****************************************************************/
1039
1040 Stem_info::Stem_info ()
1041 {
1042   ideal_y_ = shortest_y_ = 0;
1043   dir_ = CENTER;
1044 }
1045
1046 void
1047 Stem_info::scale (Real x)
1048 {
1049   ideal_y_ *= x;
1050   shortest_y_ *= x;
1051 }