2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996, 1997--1999 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
13 #include "directional-element-interface.hh"
14 #include "dimension-cache.hh"
17 #include "paper-def.hh"
18 #include "note-head.hh"
20 #include "molecule.hh"
21 #include "paper-column.hh"
25 #include "group-interface.hh"
26 #include "cross-staff.hh"
27 #include "staff-symbol-referencer.hh"
28 #include "lily-guile.icc"
32 Stem::set_beaming (int i
, Direction d
)
34 SCM pair
= get_elt_property ("beaming");
36 if (!gh_pair_p (pair
))
38 pair
= gh_cons (gh_int2scm (0),gh_int2scm (0));
39 set_elt_property ("beaming", pair
);
41 index_set_cell (pair
, d
, gh_int2scm (i
));
45 Stem::beam_count (Direction d
) const
47 SCM p
=get_elt_property ("beaming");
49 return gh_scm2int (index_cell (p
,d
));
55 Stem::head_positions () const
64 Drul_array
<Note_head
*> e (extremal_heads ());
66 return Interval (staff_symbol_referencer_interface (e
[DOWN
]).position_f (),
67 staff_symbol_referencer_interface( e
[UP
]).position_f ());
72 Stem::chord_start_f () const
74 return head_positions()[get_direction ()]
75 * Staff_symbol_referencer_interface (this).staff_space ()/2.0;
79 Stem::stem_end_position () const
81 SCM p
=get_elt_property ("stem-end-position");
85 Stem
* me
= (Stem
*) this;
86 pos
= get_default_stem_end_position ();
87 me
->set_elt_property ("stem-end-position", gh_double2scm (pos
));
90 pos
= gh_scm2double (p
);
96 Stem::get_direction () const
98 Direction d
= directional_element (this).get ();
102 Stem
* me
= (Stem
*) this;
103 d
= get_default_dir ();
104 directional_element (me
).set (d
);
111 Stem::set_stemend (Real se
)
114 Direction d
= get_direction ();
116 if (d
&& d
* head_positions()[get_direction ()] >= se
*d
)
117 warning (_ ("Weird stem size; check for narrow beams"));
119 set_elt_property ("stem-end-position", gh_double2scm (se
));
123 Stem::type_i () const
125 return first_head () ? first_head ()->balltype_i () : 2;
129 Note head that determines hshift for upstems
132 Stem::support_head ()const
134 SCM h
= get_elt_property ("support-head");
135 Score_element
* nh
= unsmob_element (h
);
138 else if (heads_i () == 1)
144 return unsmob_element (gh_car (get_elt_property ("heads")));
147 return first_head ();
152 Stem::heads_i ()const
154 Group_interface
gi (this, "heads");
159 The note head which forms one end of the stem.
162 Stem::first_head () const
164 return extremal_heads ()[-get_direction ()];
168 START is part where stem reaches `last' head.
170 Drul_array
<Note_head
*>
171 Stem::extremal_heads () const
173 const int inf
= 1000000;
174 Drul_array
<int> extpos
;
178 Drul_array
<Note_head
*> exthead
;
179 exthead
[LEFT
] = exthead
[RIGHT
] =0;
181 for (SCM s
= get_elt_property ("heads"); gh_pair_p (s
); s
= gh_cdr (s
))
183 Note_head
* n
= dynamic_cast<Note_head
*> (unsmob_element (gh_car (s
)));
184 Staff_symbol_referencer_interface
si (n
);
186 int p
= int(si
.position_f ());
190 if (d
* p
> d
* extpos
[d
])
195 } while (flip (&d
) != DOWN
);
202 Stem::add_head (Rhythmic_head
*n
)
204 n
->set_elt_property ("stem", this->self_scm_
);
205 n
->add_dependency (this);
207 Group_interface
gi (this);
208 if (Note_head
*nh
= dynamic_cast<Note_head
*> (n
))
218 set_elt_property ("heads", SCM_EOL
);
219 set_elt_property ("rests", SCM_EOL
);
221 dim_cache_
[X_AXIS
]->off_callbacks_
.push ( &Stem::off_callback
);
225 Stem::invisible_b () const
228 UGH. Who determines balltype for stem?
230 Note_head
* nh
= dynamic_cast<Note_head
*> (support_head ());
231 return !(heads_i () && nh
->balltype_i () >= 1);
235 Stem::get_center_distance (Direction d
) const
237 int staff_center
= 0;
238 int distance
= (int) (d
*(head_positions()[d
] - staff_center
));
239 return distance
>? 0;
243 Stem::get_default_dir () const
245 int du
= get_center_distance (UP
);
246 int dd
= get_center_distance (DOWN
);
249 return Direction (sign (dd
-du
));
251 return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
255 Stem::get_default_stem_end_position () const
257 bool grace_b
= to_boolean (get_elt_property ("grace"));
258 String type_str
= grace_b
? "grace-" : "";
263 SCM scm_len
= get_elt_property("length");
264 if (gh_number_p (scm_len
))
266 length_f
= gh_scm2double (scm_len
);
270 s
= ly_eval_str (type_str
+ "stem-length");
271 scm_to_array (s
, &a
);
272 // stem uses half-spaces
273 length_f
= a
[((flag_i () - 2) >? 0) <? (a
.size () - 1)] * 2;
277 s
= ly_eval_str (type_str
+ "stem-shorten");
278 scm_to_array (s
, &a
);
280 // stem uses half-spaces
282 // fixme: use gh_list_ref () iso. array[]
283 Real shorten_f
= a
[((flag_i () - 2) >? 0) <? (a
.size () - 1)] * 2;
286 'set-default-stemlen' sets direction too
288 Direction dir
= get_direction ();
291 dir
= get_default_dir ();
292 directional_element (this).set (dir
);
296 stems in unnatural (forced) direction should be shortened,
297 according to [Roush & Gourlay]
299 if (((int)chord_start_f ())
300 && (get_direction () != get_default_dir ()))
301 length_f
-= shorten_f
;
304 Real st
= head_positions()[dir
] + dir
* length_f
;
306 bool no_extend_b
= to_boolean (get_elt_property ("no-stem-extend"));
307 if (!grace_b
&& !no_extend_b
&& dir
* st
< 0)
317 Stem::flag_i () const
319 SCM s
= get_elt_property ("duration-log");
320 return (gh_number_p (s
)) ? gh_scm2int (s
) : 2;
324 Stem::position_noteheads ()
329 Link_array
<Score_element
> heads
=
330 Group_interface__extract_elements (this, (Score_element
*)0, "heads");
332 heads
.sort (compare_position
);
333 Direction dir
=get_direction ();
339 Real w
= support_head ()->extent (X_AXIS
)[dir
];
340 for (int i
=0; i
< heads
.size (); i
++)
342 heads
[i
]->translate_axis (w
- heads
[i
]->extent (X_AXIS
)[dir
], X_AXIS
);
345 bool parity
= true; // todo: make this settable.
346 int lastpos
= int (Staff_symbol_referencer_interface (heads
[0]).position_f ());
347 for (int i
=1; i
< heads
.size (); i
++)
349 Real p
= Staff_symbol_referencer_interface (heads
[i
]).position_f ();
350 int dy
=abs (lastpos
- (int)p
);
356 Real l
= heads
[i
]->extent (X_AXIS
).length ();
357 heads
[i
]->translate_axis (l
* get_direction (), X_AXIS
);
369 Stem::do_pre_processing ()
371 stem_end_position (); // ugh. Trigger direction calc.
372 position_noteheads ();
376 set_elt_property ("transparent", SCM_BOOL_T
);
381 set_spacing_hints ();
387 set stem directions for hinting the optical spacing correction.
389 Modifies DIR_LIST property of the Stem's Paper_column
391 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
394 Stem::set_spacing_hints ()
398 SCM scmdir
= gh_int2scm (get_direction ());
399 SCM dirlist
= column_l ()->get_elt_property ("dir-list");
400 if (dirlist
== SCM_UNDEFINED
)
403 if (scm_sloppy_memq (scmdir
, dirlist
) == SCM_EOL
)
405 dirlist
= gh_cons (scmdir
, dirlist
);
406 column_l ()->set_elt_property ("dir-list", dirlist
);
415 SCM st
= get_elt_property ("flag-style");
416 if ( gh_string_p (st
))
418 style
= ly_scm2string (st
);
421 char c
= (get_direction () == UP
) ? 'u' : 'd';
422 Molecule m
= lookup_l ()->afm_find (String ("flags-") + to_str (c
) +
424 if (!style
.empty_b ())
425 m
.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c
) + style
));
430 Stem::dim_callback (Dimension_cache
const* c
)
432 Stem
* s
= dynamic_cast<Stem
*> (c
->element_l ());
435 if (unsmob_element (s
->get_elt_property ("beam")) || abs (s
->flag_i ()) <= 2)
439 r
= s
->flag ().dim_
.x ();
445 const Real ANGLE
= 20* (2.0*M_PI
/360.0); // ugh!
448 Stem::do_brew_molecule_p () const
450 Molecule
*mol_p
=new Molecule
;
452 Staff_symbol_referencer_interface
si (first_head ());
454 Real y1
= si
.position_f();
455 Real y2
= stem_end_position ();
457 Interval
stem_y(y1
,y2
);
458 stem_y
.unite (Interval (y2
,y1
));
460 Real dy
= staff_symbol_referencer_interface (this)
465 head_wid
= support_head ()->extent (X_AXIS
).length ();
466 stem_y
[Direction(-get_direction ())] += get_direction () * head_wid
* tan(ANGLE
)/(2*dy
);
470 Real stem_width
= paper_l ()->get_var ("stemthickness");
471 Molecule ss
=lookup_l ()->filledbox (Box (Interval (-stem_width
/2, stem_width
/2),
472 Interval (stem_y
[DOWN
]*dy
, stem_y
[UP
]*dy
)));
473 mol_p
->add_molecule (ss
);
476 if (!beam_l () && abs (flag_i ()) > 2)
478 Molecule fl
= flag ();
479 fl
.translate_axis(stem_y
[get_direction ()]*dy
, Y_AXIS
);
480 mol_p
->add_molecule (fl
);
487 Stem::off_callback (Dimension_cache
const * c
)
489 Stem
* st
= dynamic_cast<Stem
*> (c
->element_l ());
492 if (Note_head
* f
= st
->first_head ())
494 Interval
head_wid(0, f
->extent (X_AXIS
).length ());
496 if (to_boolean (st
->get_elt_property ("stem-centered")))
497 return head_wid
.center ();
499 Real rule_thick
= st
->paper_l ()->get_var ("stemthickness");
500 Direction d
= st
->get_direction ();
501 r
= head_wid
[d
] - d
* rule_thick
;
511 SCM b
= get_elt_property ("beam");
512 return dynamic_cast<Beam
*> (unsmob_element (b
));
516 // ugh still very long.
518 Stem::calc_stem_info () const
522 Direction beam_dir
= directional_element (beam_l ()).get ();
525 programming_error ("Beam dir not set.");
529 Staff_symbol_referencer_interface
st (this);
530 Real staff_space
= st
.staff_space ();
531 Real half_space
= staff_space
/ 2;
532 Real interbeam_f
= paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
533 Real thick
= gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
534 int multiplicity
= beam_l ()->get_multiplicity ();
537 info
.idealy_f_
= chord_start_f ();
539 // for simplicity, we calculate as if dir == UP
540 info
.idealy_f_
*= beam_dir
;
541 SCM grace_prop
= get_elt_property ("grace");
543 bool grace_b
= to_boolean (grace_prop
);
547 String type_str
= grace_b
? "grace-" : "";
549 s
= ly_eval_str (type_str
+ "beamed-stem-minimum-length");
550 scm_to_array (s
, &a
);
551 Real minimum_length
= a
[multiplicity
<? (a
.size () - 1)] * staff_space
;
553 s
= ly_eval_str (type_str
+ "beamed-stem-length");
554 scm_to_array (s
, &a
);
555 Real stem_length
= a
[multiplicity
<? (a
.size () - 1)] * staff_space
;
557 if (!beam_dir
|| (beam_dir
== directional_element (this).get ()))
558 /* normal beamed stem */
562 info
.idealy_f_
+= thick
+ (multiplicity
- 1) * interbeam_f
;
564 info
.miny_f_
= info
.idealy_f_
;
565 info
.maxy_f_
= INT_MAX
;
567 info
.idealy_f_
+= stem_length
;
568 info
.miny_f_
+= minimum_length
;
571 lowest beam of (UP) beam must never be lower than second staffline
573 Hmm, reference (Wanske?)
575 Although this (additional) rule is probably correct,
576 I expect that highest beam (UP) should also never be lower
577 than middle staffline, just as normal stems.
580 bool no_extend_b
= to_boolean (get_elt_property ("no-stem-extend"));
581 if (!grace_b
&& !no_extend_b
)
583 /* highest beam of (UP) beam must never be lower than middle
585 lowest beam of (UP) beam must never be lower than second staffline
589 >? (- 2 * half_space
- thick
590 + (multiplicity
> 0) * thick
591 + interbeam_f
* (multiplicity
- 1));
597 info
.idealy_f_
-= thick
;
598 info
.maxy_f_
= info
.idealy_f_
;
599 info
.miny_f_
= -INT_MAX
;
601 info
.idealy_f_
-= stem_length
;
602 info
.maxy_f_
-= minimum_length
;
605 info
.idealy_f_
= (info
.maxy_f_
<? info
.idealy_f_
) >? info
.miny_f_
;
607 s
= beam_l ()->get_elt_property ("shorten");
609 info
.idealy_f_
-= gh_double2scm (s
);
611 Real interstaff_f
= -beam_dir
* calc_interstaff_dist (this, beam_l ());
613 info
.idealy_f_
+= interstaff_f
;
614 info
.miny_f_
+= interstaff_f
;
615 info
.maxy_f_
+= interstaff_f
;