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