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_property ("support-head");
136 Score_element * nh = unsmob_element (h);
139 else if (heads_i () == 1)
145 return unsmob_element (gh_car (get_elt_property ("heads")));
148 return first_head ();
153 Stem::heads_i ()const
155 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_property ("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_property ("stem", this->self_scm_);
206 n->add_dependency (this);
208 Group_interface gi (this);
209 if (Note_head *nh = dynamic_cast<Note_head *> (n))
219 set_elt_property ("heads", SCM_EOL);
220 set_elt_property ("rests", SCM_EOL);
222 add_offset_callback ( &Stem::off_callback, X_AXIS);
226 Stem::invisible_b () const
229 UGH. Who determines balltype for stem?
231 Note_head * nh = dynamic_cast<Note_head*> (support_head ());
232 return !(heads_i () && nh->balltype_i () >= 1);
236 Stem::get_center_distance (Direction d) const
238 int staff_center = 0;
239 int distance = (int) (d*(head_positions()[d] - staff_center));
240 return distance >? 0;
244 Stem::get_default_dir () const
246 int du = get_center_distance (UP);
247 int dd = get_center_distance (DOWN);
250 return Direction (sign (dd -du));
252 return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
256 ugh. A is used for different purposes. This functionality should be
257 moved into scheme at some point to get rid of the silly
258 conversions. (but lets wait till we have namespaces in SCM)
261 Stem::get_default_stem_end_position () const
263 bool grace_b = to_boolean (get_elt_property ("grace"));
264 String type_str = grace_b ? "grace-" : "";
269 SCM scm_len = get_elt_property("length");
270 if (gh_number_p (scm_len))
272 length_f = gh_scm2double (scm_len);
276 s = scm_eval (ly_symbol2scm ((type_str + "stem-length").ch_C()));
277 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
278 a.push (gh_scm2double (gh_car (q)));
280 // stem uses half-spaces
281 length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
286 s = scm_eval (ly_symbol2scm ((type_str + "stem-shorten").ch_C()));
287 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
288 a.push (gh_scm2double (gh_car (q)));
291 // stem uses half-spaces
293 // fixme: use gh_list_ref () iso. array[]
294 Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
297 'set-default-stemlen' sets direction too
299 Direction dir = get_direction ();
302 dir = get_default_dir ();
303 directional_element (this).set (dir);
307 stems in unnatural (forced) direction should be shortened,
308 according to [Roush & Gourlay]
310 if (((int)chord_start_f ())
311 && (get_direction () != get_default_dir ()))
312 length_f -= shorten_f;
315 Real st = head_positions()[dir] + dir * length_f;
317 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
318 if (!grace_b && !no_extend_b && dir * st < 0)
328 Stem::flag_i () const
330 SCM s = get_elt_property ("duration-log");
331 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
335 Stem::position_noteheads ()
340 Link_array<Score_element> heads =
341 Group_interface__extract_elements (this, (Score_element*)0, "heads");
343 heads.sort (compare_position);
344 Direction dir =get_direction ();
350 Real w = support_head ()->extent (X_AXIS)[dir];
351 for (int i=0; i < heads.size (); i++)
353 heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
356 bool parity= true; // todo: make this settable.
357 int lastpos = int (Staff_symbol_referencer_interface (heads[0]).position_f ());
358 for (int i=1; i < heads.size (); i ++)
360 Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
361 int dy =abs (lastpos- (int)p);
367 Real l = heads[i]->extent (X_AXIS).length ();
368 heads[i]->translate_axis (l * get_direction (), X_AXIS);
380 Stem::before_line_breaking ()
382 stem_end_position (); // ugh. Trigger direction calc.
383 position_noteheads ();
387 set_elt_property ("transparent", SCM_BOOL_T);
392 set_spacing_hints ();
398 set stem directions for hinting the optical spacing correction.
400 Modifies DIR_LIST property of the Stem's Paper_column
402 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
405 Stem::set_spacing_hints ()
409 SCM scmdir = gh_int2scm (get_direction ());
410 SCM dirlist = column_l ()->get_elt_property ("dir-list");
411 if (dirlist == SCM_UNDEFINED)
414 if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
416 dirlist = gh_cons (scmdir, dirlist);
417 column_l ()->set_elt_property ("dir-list", dirlist);
426 SCM st = get_elt_property ("flag-style");
427 if ( gh_string_p (st))
429 style = ly_scm2string (st);
432 char c = (get_direction () == UP) ? 'u' : 'd';
433 Molecule m = lookup_l ()->afm_find (String ("flags-") + to_str (c) +
435 if (!style.empty_b ())
436 m.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
441 Stem::dim_callback (Dimension_cache const* c)
443 Stem * s = dynamic_cast<Stem*> (c->element_l ());
446 if (unsmob_element (s->get_elt_property ("beam")) || abs (s->flag_i ()) <= 2)
450 r = s->flag ().dim_.x ();
456 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh!
459 Stem::do_brew_molecule () const
463 Staff_symbol_referencer_interface si (first_head ());
465 Real y1 = si.position_f();
466 Real y2 = stem_end_position ();
468 Interval stem_y(y1,y2);
469 stem_y.unite (Interval (y2,y1));
471 Real dy = staff_symbol_referencer (this).staff_space ()/2.0;
474 head_wid = support_head ()->extent (X_AXIS).length ();
475 stem_y[Direction(-get_direction ())] += get_direction () * head_wid * tan(ANGLE)/(2*dy);
479 Real stem_width = paper_l ()->get_var ("stemthickness");
480 Molecule ss =lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
481 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
482 mol.add_molecule (ss);
485 if (!beam_l () && abs (flag_i ()) > 2)
487 Molecule fl = flag ();
488 fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
489 mol.add_molecule (fl);
496 Stem::off_callback (Dimension_cache const * c)
498 Stem * st = dynamic_cast<Stem*> (c->element_l ());
501 if (Note_head * f = st->first_head ())
503 Interval head_wid(0, f->extent (X_AXIS).length ());
505 if (to_boolean (st->get_elt_property ("stem-centered")))
506 return head_wid.center ();
508 Real rule_thick = st->paper_l ()->get_var ("stemthickness");
509 Direction d = st->get_direction ();
510 r = head_wid[d] - d * rule_thick ;
520 SCM b= get_elt_property ("beam");
521 return dynamic_cast<Beam*> (unsmob_element (b));
525 // ugh still very long.
527 Stem::calc_stem_info () const
531 Direction beam_dir = directional_element (beam_l ()).get ();
534 programming_error ("Beam dir not set.");
538 Staff_symbol_referencer_interface st (this);
539 Real staff_space = st.staff_space ();
540 Real half_space = staff_space / 2;
541 Real interbeam_f = paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
542 Real thick = gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
543 int multiplicity = beam_l ()->get_multiplicity ();
546 info.idealy_f_ = chord_start_f ();
548 // for simplicity, we calculate as if dir == UP
549 info.idealy_f_ *= beam_dir;
550 SCM grace_prop = get_elt_property ("grace");
552 bool grace_b = to_boolean (grace_prop);
556 String type_str = grace_b ? "grace-" : "";
558 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-minimum-length").ch_C()));
560 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
561 a.push (gh_scm2double (gh_car (q)));
564 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
565 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
568 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
569 a.push (gh_scm2double (gh_car (q)));
571 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
573 if (!beam_dir || (beam_dir == directional_element (this).get ()))
574 /* normal beamed stem */
578 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
580 info.miny_f_ = info.idealy_f_;
581 info.maxy_f_ = INT_MAX;
583 info.idealy_f_ += stem_length;
584 info.miny_f_ += minimum_length;
587 lowest beam of (UP) beam must never be lower than second staffline
589 Hmm, reference (Wanske?)
591 Although this (additional) rule is probably correct,
592 I expect that highest beam (UP) should also never be lower
593 than middle staffline, just as normal stems.
596 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
597 if (!grace_b && !no_extend_b)
599 /* highest beam of (UP) beam must never be lower than middle
601 lowest beam of (UP) beam must never be lower than second staffline
605 >? (- 2 * half_space - thick
606 + (multiplicity > 0) * thick
607 + interbeam_f * (multiplicity - 1));
613 info.idealy_f_ -= thick;
614 info.maxy_f_ = info.idealy_f_;
615 info.miny_f_ = -INT_MAX;
617 info.idealy_f_ -= stem_length;
618 info.maxy_f_ -= minimum_length;
621 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
623 s = beam_l ()->get_elt_property ("shorten");
625 info.idealy_f_ -= gh_double2scm (s);
627 Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
629 info.idealy_f_ += interstaff_f;
630 info.miny_f_ += interstaff_f;
631 info.maxy_f_ += interstaff_f ;