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