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