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