2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
9 TODO: This is way too hairy
11 #include <math.h> // m_pi
14 #include "directional-element-interface.hh"
15 #include "note-head.hh"
18 #include "paper-def.hh"
19 #include "rhythmic-head.hh"
20 #include "font-interface.hh"
21 #include "molecule.hh"
22 #include "paper-column.hh"
26 #include "group-interface.hh"
27 #include "cross-staff.hh"
28 #include "staff-symbol-referencer.hh"
33 Stem::set_beaming (Grob
*me
,int i
, Direction d
)
35 SCM pair
= me
->get_grob_property ("beaming");
37 if (!gh_pair_p (pair
))
39 pair
= gh_cons (gh_int2scm (0),gh_int2scm (0));
40 me
-> set_grob_property ("beaming", pair
);
42 index_set_cell (pair
, d
, gh_int2scm (i
));
46 Stem::beam_count (Grob
*me
,Direction d
)
48 SCM p
=me
->get_grob_property ("beaming");
50 return gh_scm2int (index_cell (p
,d
));
56 Stem::head_positions (Grob
*me
)
64 Drul_array
<Grob
*> e (extremal_heads (me
));
66 return Interval (Staff_symbol_referencer::position_f (e
[DOWN
]),
67 Staff_symbol_referencer::position_f ( e
[UP
]));
72 Stem::chord_start_f (Grob
*me
)
74 return head_positions(me
)[get_direction (me
)]
75 * Staff_symbol_referencer::staff_space (me
)/2.0;
79 Stem::stem_end_position (Grob
*me
)
81 SCM p
=me
->get_grob_property ("stem-end-position");
86 pos
= get_default_stem_end_position (me
);
87 me
->set_grob_property ("stem-end-position", gh_double2scm (pos
));
90 pos
= gh_scm2double (p
);
96 Stem::get_direction (Grob
*me
)
98 Direction d
= Directional_element_interface::get (me
);
102 d
= get_default_dir (me
);
104 Directional_element_interface::set (me
, d
);
111 Stem::set_stemend (Grob
*me
, Real se
)
114 Direction d
= get_direction (me
);
116 if (d
&& d
* head_positions(me
)[get_direction (me
)] >= se
*d
)
117 warning (_ ("Weird stem size; check for narrow beams"));
119 me
->set_grob_property ("stem-end-position", gh_double2scm (se
));
123 Stem::type_i (Grob
*me
)
125 return first_head (me
) ? Rhythmic_head::balltype_i (first_head (me
)) : 2;
129 Note head that determines hshift for upstems
132 Stem::support_head (Grob
*me
)
134 SCM h
= me
->get_grob_property ("support-head");
135 Grob
* nh
= unsmob_grob (h
);
138 else if (heads_i (me
) == 1)
144 return unsmob_grob (gh_car (me
->get_grob_property ("heads")));
147 return first_head (me
);
152 Stem::heads_i (Grob
*me
)
154 return Pointer_group_interface::count (me
, "heads");
158 The note head which forms one end of the stem.
161 Stem::first_head (Grob
*me
)
163 return extremal_heads (me
)[-get_direction (me
)];
167 START is part where stem reaches `last' head.
170 Stem::extremal_heads (Grob
*me
)
172 const int inf
= 1000000;
173 Drul_array
<int> extpos
;
177 Drul_array
<Grob
*> exthead
;
178 exthead
[LEFT
] = exthead
[RIGHT
] =0;
180 for (SCM s
= me
->get_grob_property ("heads"); gh_pair_p (s
); s
= gh_cdr (s
))
182 Grob
* n
= unsmob_grob (gh_car (s
));
185 int p
= int(Staff_symbol_referencer::position_f (n
));
189 if (d
* p
> d
* extpos
[d
])
194 } while (flip (&d
) != DOWN
);
201 Stem::add_head (Grob
*me
, Grob
*n
)
203 n
->set_grob_property ("stem", me
->self_scm ());
204 n
->add_dependency (me
);
206 if (Note_head::has_interface (n
))
208 Pointer_group_interface::add_element (me
, "heads",n
);
212 n
->set_grob_property ("rest", n
->self_scm ());
217 Stem::invisible_b (Grob
*me
)
219 return !(heads_i (me
) && Rhythmic_head::balltype_i (support_head (me
)) >= 1);
223 Stem::get_center_distance (Grob
*me
, Direction d
)
225 int staff_center
= 0;
226 int distance
= (int) (d
*(head_positions(me
)[d
] - staff_center
));
227 return distance
>? 0;
231 Stem::get_default_dir (Grob
*me
)
233 int du
= get_center_distance (me
,UP
);
234 int dd
= get_center_distance (me
,DOWN
);
237 return Direction (sign (dd
-du
));
239 return to_dir (me
->get_grob_property ("default-neutral-direction"));
243 Stem::get_default_stem_end_position (Grob
*me
)
245 bool grace_b
= to_boolean (me
->get_grob_property ("grace"));
250 SCM scm_len
= me
->get_grob_property("length");
251 if (gh_number_p (scm_len
))
253 length_f
= gh_scm2double (scm_len
);
257 s
= me
->get_grob_property("lengths");
258 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
259 a
.push (gh_scm2double (gh_car (q
)));
261 // stem uses half-spaces
262 length_f
= a
[((flag_i (me
) - 2) >? 0) <? (a
.size () - 1)] * 2;
267 s
= me
->get_grob_property ("stem-shorten");
268 for (SCM q
= s
; gh_pair_p (q
); q
= gh_cdr (q
))
269 a
.push (gh_scm2double (gh_car (q
)));
272 // stem uses half-spaces
274 // fixme: use gh_list_ref () iso. array[]
275 Real shorten_f
= a
[((flag_i (me
) - 2) >? 0) <? (a
.size () - 1)] * 2;
278 'set-default-stemlen' sets direction too
280 Direction dir
= get_direction (me
);
283 dir
= get_default_dir (me
);
284 Directional_element_interface::set (me
, dir
);
288 stems in unnatural (forced) direction should be shortened,
289 according to [Roush & Gourlay]
291 if (((int)chord_start_f (me
))
292 && (get_direction (me
) != get_default_dir (me
)))
293 length_f
-= shorten_f
;
296 Real st
= head_positions(me
)[dir
] + dir
* length_f
;
298 bool no_extend_b
= to_boolean (me
->get_grob_property ("no-stem-extend"));
299 if (!grace_b
&& !no_extend_b
&& dir
* st
< 0) // junkme?
306 Number of hooks on the flag, ie. the log of the duration.
309 Stem::flag_i (Grob
*me
)
311 SCM s
= me
->get_grob_property ("duration-log");
312 return (gh_number_p (s
)) ? gh_scm2int (s
) : 2;
316 Stem::position_noteheads (Grob
*me
)
321 Link_array
<Grob
> heads
=
322 Pointer_group_interface__extract_elements (me
, (Grob
*)0, "heads");
324 heads
.sort (compare_position
);
325 Direction dir
=get_direction (me
);
331 Grob
*hed
= support_head (me
);
332 Real w
= hed
->extent (hed
, X_AXIS
)[dir
];
333 for (int i
=0; i
< heads
.size (); i
++)
335 heads
[i
]->translate_axis (w
- heads
[i
]->extent (heads
[i
], X_AXIS
)[dir
], X_AXIS
);
338 bool parity
= true; // todo: make me settable.
339 int lastpos
= int (Staff_symbol_referencer::position_f (heads
[0]));
340 for (int i
=1; i
< heads
.size (); i
++)
342 Real p
= Staff_symbol_referencer::position_f (heads
[i
]);
343 int dy
=abs (lastpos
- (int)p
);
349 Real l
= heads
[i
]->extent (heads
[i
], X_AXIS
).length ();
350 heads
[i
]->translate_axis (l
* get_direction (me
), X_AXIS
);
361 MAKE_SCHEME_CALLBACK(Stem
,before_line_breaking
,1);
363 Stem::before_line_breaking (SCM smob
)
365 Grob
*me
= unsmob_grob (smob
);
366 stem_end_position (me
); // ugh. Trigger direction calc.
367 position_noteheads (me
);
369 if (invisible_b (me
))
371 me
->remove_grob_property ("molecule-callback");
375 set_spacing_hints (me
);
376 return SCM_UNSPECIFIED
;
382 When in a beam with tuplet brackets, brew_mol is called early,
383 caching a wrong value.
385 MAKE_SCHEME_CALLBACK (Stem
, height
, 2);
387 Stem::height (SCM smob
, SCM ax
)
389 Axis a
= (Axis
)gh_scm2int (ax
);
390 Grob
* me
= unsmob_grob (smob
);
391 assert ( a
== Y_AXIS
);
393 SCM mol
= me
->get_uncached_molecule ();
396 iv
= unsmob_molecule (mol
)->extent (a
);
397 return ly_interval2scm (iv
);
402 set stem directions for hinting the optical spacing correction.
404 Modifies DIR_LIST property of the Stem's Paper_column
406 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
409 Stem::set_spacing_hints (Grob
*me
)
411 if (!invisible_b (me
))
413 SCM scmdir
= gh_int2scm (get_direction (me
));
415 Item
* item
= dynamic_cast<Item
*> (me
);
416 Item
* col
= item
->column_l ();
417 SCM dirlist
=col
->get_grob_property ("dir-list");
418 if (scm_sloppy_memq (scmdir
, dirlist
) == SCM_BOOL_F
)
420 dirlist
= gh_cons (scmdir
, dirlist
);
421 col
->set_grob_property ("dir-list", dirlist
);
430 SCM st
= me
->get_grob_property ("flag-style");
431 if ( gh_string_p (st
))
433 style
= ly_scm2string (st
);
436 char c
= (get_direction (me
) == UP
) ? 'u' : 'd';
437 Molecule m
= Font_interface::get_default_font (me
)->find_by_name (String ("flags-") + to_str (c
) +
438 to_str (flag_i (me
)));
439 if (!style
.empty_b ())
440 m
.add_molecule(Font_interface::get_default_font (me
)->find_by_name (String ("flags-") + to_str (c
) + style
));
444 MAKE_SCHEME_CALLBACK(Stem
,dim_callback
,2);
446 Stem::dim_callback (SCM e
, SCM ax
)
448 Axis a
= (Axis
) gh_scm2int (ax
);
449 assert (a
== X_AXIS
);
450 Grob
*se
= unsmob_grob (e
);
452 if (unsmob_grob (se
->get_grob_property ("beam")) || abs (flag_i (se
)) <= 2)
456 r
= flag (se
).extent (X_AXIS
);
458 return ly_interval2scm ( r
);
462 const Real ANGLE
= 20* (2.0*M_PI
/360.0); // ugh! Should be settable.
465 MAKE_SCHEME_CALLBACK(Stem
,brew_molecule
,1);
468 Stem::brew_molecule (SCM smob
)
470 Grob
*me
= unsmob_grob (smob
);
472 Direction d
= get_direction (me
);
475 Real y1
= Staff_symbol_referencer::position_f (first_head (me
));
476 Real y2
= stem_end_position (me
);
478 Interval
stem_y(y1
,y2
);
479 stem_y
.unite (Interval (y2
,y1
));
481 Real dy
= Staff_symbol_referencer::staff_space (me
)/2.0;
484 if (Grob
*hed
= support_head (me
))
485 head_wid
= hed
->extent (hed
,X_AXIS
).length ();
486 stem_y
[Direction(-d
)] += d
* head_wid
* tan(ANGLE
)/(2*dy
);
488 if (!invisible_b (me
))
490 Real stem_width
= gh_scm2double (me
->get_grob_property ("thickness")) * me
->paper_l ()->get_var ("stafflinethickness");
491 Molecule ss
=Lookup::filledbox (Box (Interval (-stem_width
/2, stem_width
/2),
492 Interval (stem_y
[DOWN
]*dy
, stem_y
[UP
]*dy
)));
493 mol
.add_molecule (ss
);
496 if (!beam_l (me
) && abs (flag_i (me
)) > 2)
498 Molecule fl
= flag (me
);
499 fl
.translate_axis(stem_y
[d
]*dy
, Y_AXIS
);
500 mol
.add_molecule (fl
);
503 return mol
.smobbed_copy ();
506 MAKE_SCHEME_CALLBACK(Stem
,off_callback
,2);
508 Stem::off_callback (SCM element_smob
, SCM
)
510 Grob
*me
= unsmob_grob (element_smob
);
513 if (Grob
* f
= first_head (me
))
515 Interval
head_wid(0, f
->extent (f
,X_AXIS
).length ());
517 if (to_boolean (me
->get_grob_property ("stem-centered")))
518 return gh_double2scm ( head_wid
.center ());
520 Real rule_thick
= gh_scm2double (me
->get_grob_property ("thickness")) * me
->paper_l ()->get_var ("stafflinethickness");
521 Direction d
= get_direction (me
);
522 r
= head_wid
[d
] - d
* rule_thick
;
524 return gh_double2scm (r
);
530 Stem::beam_l (Grob
*me
)
532 SCM b
= me
->get_grob_property ("beam");
533 return unsmob_grob (b
);
537 // ugh still very long.
539 Stem::calc_stem_info (Grob
*me
)
541 Grob
* beam
= beam_l (me
);
543 Direction beam_dir
= Directional_element_interface::get (beam
);
546 programming_error ("Beam dir not set.");
551 Real staff_space
= Staff_symbol_referencer::staff_space (me
);
552 Real half_space
= staff_space
/ 2;
553 int multiplicity
= Beam::get_multiplicity (beam
);
556 SCM space_proc
= beam
->get_grob_property ("space-function");
557 SCM space
= gh_call1 (space_proc
, gh_int2scm (multiplicity
));
558 Real interbeam_f
= gh_scm2double (space
) * staff_space
;
560 Real thick
= gh_scm2double (beam
->get_grob_property ("thickness"));
562 info
.idealy_f_
= chord_start_f (me
);
564 // for simplicity, we calculate as if dir == UP
565 info
.idealy_f_
*= beam_dir
;
566 SCM grace_prop
= me
->get_grob_property ("grace");
568 bool grace_b
= to_boolean (grace_prop
);
573 s
= me
->get_grob_property("beamed-minimum-lengths");
575 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
576 a
.push (gh_scm2double (gh_car (q
)));
579 Real minimum_length
= a
[multiplicity
<? (a
.size () - 1)] * staff_space
;
580 s
= me
->get_grob_property ("beamed-lengths");
583 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
584 a
.push (gh_scm2double (gh_car (q
)));
586 Real stem_length
= a
[multiplicity
<? (a
.size () - 1)] * staff_space
;
588 if (!beam_dir
|| (beam_dir
== Directional_element_interface::get (me
)))
589 /* normal beamed stem */
593 info
.idealy_f_
+= thick
+ (multiplicity
- 1) * interbeam_f
;
595 info
.miny_f_
= info
.idealy_f_
;
596 info
.maxy_f_
= INT_MAX
;
598 info
.idealy_f_
+= stem_length
;
599 info
.miny_f_
+= minimum_length
;
602 lowest beam of (UP) beam must never be lower than second staffline
604 Hmm, reference (Wanske?)
606 Although this (additional) rule is probably correct,
607 I expect that highest beam (UP) should also never be lower
608 than middle staffline, just as normal stems.
611 bool no_extend_b
= to_boolean (me
->get_grob_property ("no-stem-extend"));
612 if (!grace_b
&& !no_extend_b
)
614 /* highest beam of (UP) beam must never be lower than middle
616 lowest beam of (UP) beam must never be lower than second staffline
620 >? (- 2 * half_space
- thick
621 + (multiplicity
> 0) * thick
622 + interbeam_f
* (multiplicity
- 1));
628 info
.idealy_f_
-= thick
;
629 info
.maxy_f_
= info
.idealy_f_
;
630 info
.miny_f_
= -INT_MAX
;
632 info
.idealy_f_
-= stem_length
;
633 info
.maxy_f_
-= minimum_length
;
636 info
.idealy_f_
= (info
.maxy_f_
<? info
.idealy_f_
) >? info
.miny_f_
;
638 s
= beam
->get_grob_property ("shorten");
640 info
.idealy_f_
-= gh_scm2double (s
);
642 Real interstaff_f
= -beam_dir
* calc_interstaff_dist (dynamic_cast<Item
*> (me
), dynamic_cast<Spanner
*> (beam
));
644 info
.idealy_f_
+= interstaff_f
;
645 info
.miny_f_
+= interstaff_f
;
646 info
.maxy_f_
+= interstaff_f
;
652 Stem::has_interface (Grob
*m
)
654 return m
&& m
->has_interface (ly_symbol2scm ("stem-interface"));
658 Stem::set_interface (Grob
*me
)
660 me
->set_interface (ly_symbol2scm ("stem-interface"));