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
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"
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 (e
[DOWN
]).position_f (),
67 staff_symbol_referencer( 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_interface (this).get ();
102 Stem
* me
= (Stem
*) this;
103 d
= get_default_dir ();
105 Directional_element_interface (me
).set (d
);
112 Stem::set_stemend (Real se
)
115 Direction d
= get_direction ();
117 if (d
&& d
* head_positions()[get_direction ()] >= se
*d
)
118 warning (_ ("Weird stem size; check for narrow beams"));
120 set_elt_property ("stem-end-position", gh_double2scm (se
));
124 Stem::type_i () const
126 return first_head () ? first_head ()->balltype_i () : 2;
130 Note head that determines hshift for upstems
133 Stem::support_head ()const
135 SCM h
= get_elt_pointer ("support-head");
136 Score_element
* nh
= unsmob_element (h
);
139 else if (heads_i () == 1)
145 return unsmob_element (gh_car (get_elt_pointer ("heads")));
148 return first_head ();
153 Stem::heads_i ()const
155 Pointer_group_interface
gi (this, "heads");
160 The note head which forms one end of the stem.
163 Stem::first_head () const
165 return extremal_heads ()[-get_direction ()];
169 START is part where stem reaches `last' head.
171 Drul_array
<Note_head
*>
172 Stem::extremal_heads () const
174 const int inf
= 1000000;
175 Drul_array
<int> extpos
;
179 Drul_array
<Note_head
*> exthead
;
180 exthead
[LEFT
] = exthead
[RIGHT
] =0;
182 for (SCM s
= get_elt_pointer ("heads"); gh_pair_p (s
); s
= gh_cdr (s
))
184 Note_head
* n
= dynamic_cast<Note_head
*> (unsmob_element (gh_car (s
)));
185 Staff_symbol_referencer_interface
si (n
);
187 int p
= int(si
.position_f ());
191 if (d
* p
> d
* extpos
[d
])
196 } while (flip (&d
) != DOWN
);
203 Stem::add_head (Rhythmic_head
*n
)
205 n
->set_elt_pointer ("stem", this->self_scm_
);
206 n
->add_dependency (this);
208 if (Note_head
*nh
= dynamic_cast<Note_head
*> (n
))
210 Pointer_group_interface
gi (this);
217 n
->set_elt_pointer ("rest", n
->self_scm_
);
224 set_elt_pointer ("heads", SCM_EOL
);
226 add_offset_callback ( &Stem::off_callback
, X_AXIS
);
230 Stem::invisible_b () const
233 UGH. Who determines balltype for stem?
235 Note_head
* nh
= dynamic_cast<Note_head
*> (support_head ());
236 return !(heads_i () && nh
->balltype_i () >= 1);
240 Stem::get_center_distance (Direction d
) const
242 int staff_center
= 0;
243 int distance
= (int) (d
*(head_positions()[d
] - staff_center
));
244 return distance
>? 0;
248 Stem::get_default_dir () const
250 int du
= get_center_distance (UP
);
251 int dd
= get_center_distance (DOWN
);
254 return Direction (sign (dd
-du
));
256 return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
260 ugh. A is used for different purposes. This functionality should be
261 moved into scheme at some point to get rid of the silly
262 conversions. (but lets wait till we have namespaces in SCM)
265 Stem::get_default_stem_end_position () const
267 bool grace_b
= to_boolean (get_elt_property ("grace"));
268 String type_str
= grace_b
? "grace-" : "";
273 SCM scm_len
= get_elt_property("length");
274 if (gh_number_p (scm_len
))
276 length_f
= gh_scm2double (scm_len
);
280 s
= scm_eval (ly_symbol2scm ((type_str
+ "stem-length").ch_C()));
281 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
282 a
.push (gh_scm2double (gh_car (q
)));
284 // stem uses half-spaces
285 length_f
= a
[((flag_i () - 2) >? 0) <? (a
.size () - 1)] * 2;
290 s
= scm_eval (ly_symbol2scm ((type_str
+ "stem-shorten").ch_C()));
291 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
292 a
.push (gh_scm2double (gh_car (q
)));
295 // stem uses half-spaces
297 // fixme: use gh_list_ref () iso. array[]
298 Real shorten_f
= a
[((flag_i () - 2) >? 0) <? (a
.size () - 1)] * 2;
301 'set-default-stemlen' sets direction too
303 Direction dir
= get_direction ();
306 dir
= get_default_dir ();
307 Directional_element_interface (this).set (dir
);
311 stems in unnatural (forced) direction should be shortened,
312 according to [Roush & Gourlay]
314 if (((int)chord_start_f ())
315 && (get_direction () != get_default_dir ()))
316 length_f
-= shorten_f
;
319 Real st
= head_positions()[dir
] + dir
* length_f
;
321 bool no_extend_b
= to_boolean (get_elt_property ("no-stem-extend"));
322 if (!grace_b
&& !no_extend_b
&& dir
* st
< 0)
332 Stem::flag_i () const
334 SCM s
= get_elt_property ("duration-log");
335 return (gh_number_p (s
)) ? gh_scm2int (s
) : 2;
339 Stem::position_noteheads ()
344 Link_array
<Score_element
> heads
=
345 Pointer_group_interface__extract_elements (this, (Score_element
*)0, "heads");
347 heads
.sort (compare_position
);
348 Direction dir
=get_direction ();
354 Real w
= support_head ()->extent (X_AXIS
)[dir
];
355 for (int i
=0; i
< heads
.size (); i
++)
357 heads
[i
]->translate_axis (w
- heads
[i
]->extent (X_AXIS
)[dir
], X_AXIS
);
360 bool parity
= true; // todo: make this settable.
361 int lastpos
= int (Staff_symbol_referencer_interface (heads
[0]).position_f ());
362 for (int i
=1; i
< heads
.size (); i
++)
364 Real p
= Staff_symbol_referencer_interface (heads
[i
]).position_f ();
365 int dy
=abs (lastpos
- (int)p
);
371 Real l
= heads
[i
]->extent (X_AXIS
).length ();
372 heads
[i
]->translate_axis (l
* get_direction (), X_AXIS
);
383 GLUE_SCORE_ELEMENT(Stem
,before_line_breaking
);
385 Stem::member_before_line_breaking ()
387 stem_end_position (); // ugh. Trigger direction calc.
388 position_noteheads ();
392 remove_elt_property ("molecule-callback");
396 set_spacing_hints ();
397 return SCM_UNDEFINED
;
403 set stem directions for hinting the optical spacing correction.
405 Modifies DIR_LIST property of the Stem's Paper_column
407 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
410 Stem::set_spacing_hints ()
414 SCM scmdir
= gh_int2scm (get_direction ());
415 SCM dirlist
= column_l ()->get_elt_property ("dir-list");
416 if (dirlist
== SCM_UNDEFINED
)
419 if (scm_sloppy_memq (scmdir
, dirlist
) == SCM_EOL
)
421 dirlist
= gh_cons (scmdir
, dirlist
);
422 column_l ()->set_elt_property ("dir-list", dirlist
);
431 SCM st
= get_elt_property ("flag-style");
432 if ( gh_string_p (st
))
434 style
= ly_scm2string (st
);
437 char c
= (get_direction () == UP
) ? 'u' : 'd';
438 Molecule m
= lookup_l ()->afm_find (String ("flags-") + to_str (c
) +
440 if (!style
.empty_b ())
441 m
.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c
) + style
));
446 Stem::dim_callback (Score_element
const *se
, Axis
)
448 Stem
* s
= dynamic_cast<Stem
*> ((Score_element
*)se
);
451 if (unsmob_element (s
->get_elt_pointer ("beam")) || abs (s
->flag_i ()) <= 2)
455 r
= s
->flag ().extent (X_AXIS
);
461 const Real ANGLE
= 20* (2.0*M_PI
/360.0); // ugh! Should be settable.
464 GLUE_SCORE_ELEMENT(Stem
,brew_molecule
);
467 Stem::member_brew_molecule () const
471 Staff_symbol_referencer_interface
si (first_head ());
473 Real y1
= si
.position_f();
474 Real y2
= stem_end_position ();
476 Interval
stem_y(y1
,y2
);
477 stem_y
.unite (Interval (y2
,y1
));
479 Real dy
= staff_symbol_referencer (this).staff_space ()/2.0;
482 head_wid
= support_head ()->extent (X_AXIS
).length ();
483 stem_y
[Direction(-get_direction ())] += get_direction () * head_wid
* tan(ANGLE
)/(2*dy
);
487 Real stem_width
= paper_l ()->get_var ("stemthickness");
488 Molecule ss
=lookup_l ()->filledbox (Box (Interval (-stem_width
/2, stem_width
/2),
489 Interval (stem_y
[DOWN
]*dy
, stem_y
[UP
]*dy
)));
490 mol
.add_molecule (ss
);
493 if (!beam_l () && abs (flag_i ()) > 2)
495 Molecule fl
= flag ();
496 fl
.translate_axis(stem_y
[get_direction ()]*dy
, Y_AXIS
);
497 mol
.add_molecule (fl
);
500 return mol
.create_scheme();
504 Stem::off_callback (Score_element
const* se
, Axis
)
506 Stem
*st
= dynamic_cast<Stem
*> ((Score_element
*)se
);
509 if (Note_head
* f
= st
->first_head ())
511 Interval
head_wid(0, f
->extent (X_AXIS
).length ());
513 if (to_boolean (st
->get_elt_property ("stem-centered")))
514 return head_wid
.center ();
516 Real rule_thick
= st
->paper_l ()->get_var ("stemthickness");
517 Direction d
= st
->get_direction ();
518 r
= head_wid
[d
] - d
* rule_thick
;
528 SCM b
= get_elt_pointer ("beam");
529 return dynamic_cast<Beam
*> (unsmob_element (b
));
533 // ugh still very long.
535 Stem::calc_stem_info () const
539 Direction beam_dir
= Directional_element_interface (beam_l ()).get ();
542 programming_error ("Beam dir not set.");
546 Staff_symbol_referencer_interface
st (this);
547 Real staff_space
= st
.staff_space ();
548 Real half_space
= staff_space
/ 2;
549 Real interbeam_f
= paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
550 Real thick
= gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
551 int multiplicity
= beam_l ()->get_multiplicity ();
554 info
.idealy_f_
= chord_start_f ();
556 // for simplicity, we calculate as if dir == UP
557 info
.idealy_f_
*= beam_dir
;
558 SCM grace_prop
= get_elt_property ("grace");
560 bool grace_b
= to_boolean (grace_prop
);
564 String type_str
= grace_b
? "grace-" : "";
566 s
= scm_eval (ly_symbol2scm ((type_str
+ "beamed-stem-minimum-length").ch_C()));
568 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
569 a
.push (gh_scm2double (gh_car (q
)));
572 Real minimum_length
= a
[multiplicity
<? (a
.size () - 1)] * staff_space
;
573 s
= scm_eval (ly_symbol2scm ((type_str
+ "beamed-stem-length").ch_C()));
576 for (SCM q
= s
; q
!= SCM_EOL
; q
= gh_cdr (q
))
577 a
.push (gh_scm2double (gh_car (q
)));
579 Real stem_length
= a
[multiplicity
<? (a
.size () - 1)] * staff_space
;
581 if (!beam_dir
|| (beam_dir
== Directional_element_interface (this).get ()))
582 /* normal beamed stem */
586 info
.idealy_f_
+= thick
+ (multiplicity
- 1) * interbeam_f
;
588 info
.miny_f_
= info
.idealy_f_
;
589 info
.maxy_f_
= INT_MAX
;
591 info
.idealy_f_
+= stem_length
;
592 info
.miny_f_
+= minimum_length
;
595 lowest beam of (UP) beam must never be lower than second staffline
597 Hmm, reference (Wanske?)
599 Although this (additional) rule is probably correct,
600 I expect that highest beam (UP) should also never be lower
601 than middle staffline, just as normal stems.
604 bool no_extend_b
= to_boolean (get_elt_property ("no-stem-extend"));
605 if (!grace_b
&& !no_extend_b
)
607 /* highest beam of (UP) beam must never be lower than middle
609 lowest beam of (UP) beam must never be lower than second staffline
613 >? (- 2 * half_space
- thick
614 + (multiplicity
> 0) * thick
615 + interbeam_f
* (multiplicity
- 1));
621 info
.idealy_f_
-= thick
;
622 info
.maxy_f_
= info
.idealy_f_
;
623 info
.miny_f_
= -INT_MAX
;
625 info
.idealy_f_
-= stem_length
;
626 info
.maxy_f_
-= minimum_length
;
629 info
.idealy_f_
= (info
.maxy_f_
<? info
.idealy_f_
) >? info
.miny_f_
;
631 s
= beam_l ()->get_elt_property ("shorten");
633 info
.idealy_f_
-= gh_scm2double (s
);
635 Real interstaff_f
= -beam_dir
* calc_interstaff_dist (this, beam_l ());
637 info
.idealy_f_
+= interstaff_f
;
638 info
.miny_f_
+= interstaff_f
;
639 info
.maxy_f_
+= interstaff_f
;