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 (this).get ();
102 Stem * me = (Stem*) this;
103 d = get_default_dir ();
105 directional_element (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 Pointer_group_interface gi (this);
209 if (Note_head *nh = dynamic_cast<Note_head *> (n))
220 set_elt_pointer ("heads", SCM_EOL);
221 set_elt_pointer ("rests", SCM_EOL);
223 add_offset_callback ( &Stem::off_callback, X_AXIS);
227 Stem::invisible_b () const
230 UGH. Who determines balltype for stem?
232 Note_head * nh = dynamic_cast<Note_head*> (support_head ());
233 return !(heads_i () && nh->balltype_i () >= 1);
237 Stem::get_center_distance (Direction d) const
239 int staff_center = 0;
240 int distance = (int) (d*(head_positions()[d] - staff_center));
241 return distance >? 0;
245 Stem::get_default_dir () const
247 int du = get_center_distance (UP);
248 int dd = get_center_distance (DOWN);
251 return Direction (sign (dd -du));
253 return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
257 ugh. A is used for different purposes. This functionality should be
258 moved into scheme at some point to get rid of the silly
259 conversions. (but lets wait till we have namespaces in SCM)
262 Stem::get_default_stem_end_position () const
264 bool grace_b = to_boolean (get_elt_property ("grace"));
265 String type_str = grace_b ? "grace-" : "";
270 SCM scm_len = get_elt_property("length");
271 if (gh_number_p (scm_len))
273 length_f = gh_scm2double (scm_len);
277 s = scm_eval (ly_symbol2scm ((type_str + "stem-length").ch_C()));
278 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
279 a.push (gh_scm2double (gh_car (q)));
281 // stem uses half-spaces
282 length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
287 s = scm_eval (ly_symbol2scm ((type_str + "stem-shorten").ch_C()));
288 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
289 a.push (gh_scm2double (gh_car (q)));
292 // stem uses half-spaces
294 // fixme: use gh_list_ref () iso. array[]
295 Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
298 'set-default-stemlen' sets direction too
300 Direction dir = get_direction ();
303 dir = get_default_dir ();
304 directional_element (this).set (dir);
308 stems in unnatural (forced) direction should be shortened,
309 according to [Roush & Gourlay]
311 if (((int)chord_start_f ())
312 && (get_direction () != get_default_dir ()))
313 length_f -= shorten_f;
316 Real st = head_positions()[dir] + dir * length_f;
318 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
319 if (!grace_b && !no_extend_b && dir * st < 0)
329 Stem::flag_i () const
331 SCM s = get_elt_property ("duration-log");
332 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
336 Stem::position_noteheads ()
341 Link_array<Score_element> heads =
342 Pointer_group_interface__extract_elements (this, (Score_element*)0, "heads");
344 heads.sort (compare_position);
345 Direction dir =get_direction ();
351 Real w = support_head ()->extent (X_AXIS)[dir];
352 for (int i=0; i < heads.size (); i++)
354 heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
357 bool parity= true; // todo: make this settable.
358 int lastpos = int (Staff_symbol_referencer_interface (heads[0]).position_f ());
359 for (int i=1; i < heads.size (); i ++)
361 Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
362 int dy =abs (lastpos- (int)p);
368 Real l = heads[i]->extent (X_AXIS).length ();
369 heads[i]->translate_axis (l * get_direction (), X_AXIS);
381 Stem::before_line_breaking ()
383 stem_end_position (); // ugh. Trigger direction calc.
384 position_noteheads ();
388 set_elt_property ("transparent", SCM_BOOL_T);
389 set_extent_callback (0, Y_AXIS);
390 set_extent_callback (0, X_AXIS);
393 set_spacing_hints ();
399 set stem directions for hinting the optical spacing correction.
401 Modifies DIR_LIST property of the Stem's Paper_column
403 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
406 Stem::set_spacing_hints ()
410 SCM scmdir = gh_int2scm (get_direction ());
411 SCM dirlist = column_l ()->get_elt_property ("dir-list");
412 if (dirlist == SCM_UNDEFINED)
415 if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
417 dirlist = gh_cons (scmdir, dirlist);
418 column_l ()->set_elt_property ("dir-list", dirlist);
427 SCM st = get_elt_property ("flag-style");
428 if ( gh_string_p (st))
430 style = ly_scm2string (st);
433 char c = (get_direction () == UP) ? 'u' : 'd';
434 Molecule m = lookup_l ()->afm_find (String ("flags-") + to_str (c) +
436 if (!style.empty_b ())
437 m.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
442 Stem::dim_callback (Score_element const *se, Axis )
444 Stem * s = dynamic_cast<Stem*> ((Score_element*)se);
447 if (unsmob_element (s->get_elt_pointer ("beam")) || abs (s->flag_i ()) <= 2)
451 r = s->flag ().extent (X_AXIS);
457 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh!
460 Stem::do_brew_molecule () const
464 Staff_symbol_referencer_interface si (first_head ());
466 Real y1 = si.position_f();
467 Real y2 = stem_end_position ();
469 Interval stem_y(y1,y2);
470 stem_y.unite (Interval (y2,y1));
472 Real dy = staff_symbol_referencer (this).staff_space ()/2.0;
475 head_wid = support_head ()->extent (X_AXIS).length ();
476 stem_y[Direction(-get_direction ())] += get_direction () * head_wid * tan(ANGLE)/(2*dy);
480 Real stem_width = paper_l ()->get_var ("stemthickness");
481 Molecule ss =lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
482 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
483 mol.add_molecule (ss);
486 if (!beam_l () && abs (flag_i ()) > 2)
488 Molecule fl = flag ();
489 fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
490 mol.add_molecule (fl);
497 Stem::off_callback (Score_element const* se, Axis)
499 Stem *st = dynamic_cast<Stem*> ((Score_element*)se);
502 if (Note_head * f = st->first_head ())
504 Interval head_wid(0, f->extent (X_AXIS).length ());
506 if (to_boolean (st->get_elt_property ("stem-centered")))
507 return head_wid.center ();
509 Real rule_thick = st->paper_l ()->get_var ("stemthickness");
510 Direction d = st->get_direction ();
511 r = head_wid[d] - d * rule_thick ;
521 SCM b= get_elt_pointer ("beam");
522 return dynamic_cast<Beam*> (unsmob_element (b));
526 // ugh still very long.
528 Stem::calc_stem_info () const
532 Direction beam_dir = directional_element (beam_l ()).get ();
535 programming_error ("Beam dir not set.");
539 Staff_symbol_referencer_interface st (this);
540 Real staff_space = st.staff_space ();
541 Real half_space = staff_space / 2;
542 Real interbeam_f = paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
543 Real thick = gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
544 int multiplicity = beam_l ()->get_multiplicity ();
547 info.idealy_f_ = chord_start_f ();
549 // for simplicity, we calculate as if dir == UP
550 info.idealy_f_ *= beam_dir;
551 SCM grace_prop = get_elt_property ("grace");
553 bool grace_b = to_boolean (grace_prop);
557 String type_str = grace_b ? "grace-" : "";
559 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-minimum-length").ch_C()));
561 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
562 a.push (gh_scm2double (gh_car (q)));
565 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
566 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
569 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
570 a.push (gh_scm2double (gh_car (q)));
572 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
574 if (!beam_dir || (beam_dir == directional_element (this).get ()))
575 /* normal beamed stem */
579 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
581 info.miny_f_ = info.idealy_f_;
582 info.maxy_f_ = INT_MAX;
584 info.idealy_f_ += stem_length;
585 info.miny_f_ += minimum_length;
588 lowest beam of (UP) beam must never be lower than second staffline
590 Hmm, reference (Wanske?)
592 Although this (additional) rule is probably correct,
593 I expect that highest beam (UP) should also never be lower
594 than middle staffline, just as normal stems.
597 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
598 if (!grace_b && !no_extend_b)
600 /* highest beam of (UP) beam must never be lower than middle
602 lowest beam of (UP) beam must never be lower than second staffline
606 >? (- 2 * half_space - thick
607 + (multiplicity > 0) * thick
608 + interbeam_f * (multiplicity - 1));
614 info.idealy_f_ -= thick;
615 info.maxy_f_ = info.idealy_f_;
616 info.miny_f_ = -INT_MAX;
618 info.idealy_f_ -= stem_length;
619 info.maxy_f_ -= minimum_length;
622 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
624 s = beam_l ()->get_elt_property ("shorten");
626 info.idealy_f_ -= gh_double2scm (s);
628 Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
630 info.idealy_f_ += interstaff_f;
631 info.miny_f_ += interstaff_f;
632 info.maxy_f_ += interstaff_f ;