2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--1999 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
20 The relationship Stem <-> Beam is way too hairy. Let's figure who
21 needs what, and what information should be available when.
29 #include "dimensions.hh"
33 #include "molecule.hh"
34 #include "leastsquares.hh"
36 #include "paper-def.hh"
38 #include "group-interface.hh"
42 Group_interface g (this, "stems");
51 TODO: Fix this class. This is wildly inefficient.
54 Beam::stem (int i)const
56 return Group_interface__extract_elements ((Beam*) this, (Stem*) 0, "stems")[i];
60 Beam::stem_count ()const
62 Group_interface gi (this, "stems");
67 Beam::stem_top ()const
69 return Group_interface__extract_elements ((Beam*) this, (Stem*) 0, "stems")[stem_count () - 1];
74 Beam::add_stem (Stem*s)
76 Group_interface gi (this, "stems");
79 s->add_dependency (this);
81 assert (!s->beam_l ());
82 s->set_elt_property ("beam", self_scm_);
84 if (!spanned_drul_[LEFT])
91 Beam::do_brew_molecule_p () const
93 Molecule *mol_p = new Molecule;
97 Real x0 = stem (0)->hpos_f ();
98 for (int j=0; j <stem_count (); j++)
101 Stem * prev = (j > 0)? stem (j-1) : 0;
102 Stem * next = (j < stem_count ()-1) ? stem (j+1) :0;
104 Molecule sb = stem_beams (i, next, prev);
105 Real x = i->hpos_f ()-x0;
106 sb.translate (Offset (x, x * slope_f_ + left_y_));
107 mol_p->add_molecule (sb);
109 mol_p->translate_axis (x0
110 - spanned_drul_[LEFT]->relative_coordinate (0, X_AXIS), X_AXIS);
116 Beam::center () const
118 Real w = (stem (0)->note_delta_f () + extent (X_AXIS).length ())/2.0;
119 return Offset (w, w * slope_f_);
123 Simplistic auto-knees; only consider vertical gap between two
127 Beam::auto_knee (SCM gap, bool interstaff_b)
131 if (gap != SCM_UNDEFINED)
133 int auto_gap_i = gh_scm2int (gap);
134 for (int i=1; i < stem_count (); i++)
136 bool is_b = (bool)(stem (i)->get_real ("interstaff-f") - stem (i-1)->get_real ("interstaff-f"));
137 int l_y = (int)(stem (i-1)->chord_start_f ())
138 + (int)stem (i-1)->get_real ("interstaff-f");
139 int r_y = (int)(stem (i)->chord_start_f ())
140 + (int)stem (i)->get_real ("interstaff-f");
141 int gap_i = r_y - l_y;
144 Forced stem directions are ignored. If you don't want auto-knees,
145 don't set, or unset autoKneeGap/autoInterstaffKneeGap.
147 if ((abs (gap_i) >= auto_gap_i) && (!interstaff_b || is_b))
149 knee_y = (r_y + l_y) / 2;
157 for (int i=0; i < stem_count (); i++)
159 int y = (int)(stem (i)->chord_start_f ())
160 + (int)stem (i)->get_real ("interstaff-f");
161 stem (i)->set_direction ( y < knee_y ? UP : DOWN);
162 stem (i)->set_elt_property ("dir-forced", SCM_BOOL_T);
171 if (auto_knee (get_elt_property ("auto-interstaff-knee-gap"), true))
174 return auto_knee (get_elt_property ("auto-knee-gap"), false);
179 Beam::do_pre_processing ()
182 urg: it seems that info on whether beam (voice) dir was forced
183 is being junked here?
185 if (!get_direction ())
186 set_direction ( get_default_dir ());
188 set_direction (get_direction ());
192 Beam::do_print () const
195 DEBUG_OUT << "slope_f_ " << slope_f_ << "left ypos " << left_y_;
196 Spanner::do_print ();
201 Beam::do_post_processing ()
203 if (stem_count () < 2)
205 warning (_ ("beam with less than two stems"));
206 set_elt_property ("transparent", SCM_BOOL_T);
213 if auto-knee did its work, most probably stem directions
214 have changed, so we must recalculate all.
216 set_direction (get_default_dir ());
217 set_direction (get_direction ());
219 /* auto-knees used to only work for slope = 0
220 anyway, should be able to set slope per beam
221 set_elt_property ("damping", gh_int2scm(1000));
233 Beam::do_width () const
235 return Interval (stem (0)->hpos_f (),
236 stems_.top ()->hpos_f ());
241 Beam::get_default_dir () const
243 Drul_array<int> total;
244 total[UP] = total[DOWN] = 0;
245 Drul_array<int> count;
246 count[UP] = count[DOWN] = 0;
249 for (int i=0; i <stem_count (); i++)
252 int current = s->get_direction ()
253 ? (1 + d * s->get_direction ())/2
254 : s->get_center_distance ((Direction)-d);
262 } while (flip(&d) != DOWN);
265 [Ross] states that the majority of the notes dictates the
266 direction (and not the mean of "center distance")
268 But is that because it really looks better, or because he wants
269 to provide some real simple hands-on rules?
271 We have our doubts, so we simply provide all sensible alternatives.
273 If dir is not determined: up (see stem::get_default_dir ()) */
275 Direction beam_dir = CENTER;
276 Direction neutral_dir = (Direction)(int)paper_l ()->get_var ("stem_default_neutral_direction");
278 SCM a = get_elt_property ("beam-dir-algorithm");
280 if (a == ly_symbol2scm ("majority")) // should get default from paper.
281 beam_dir = (count[UP] == count[DOWN]) ? neutral_dir
282 : (count[UP] > count[DOWN]) ? UP : DOWN;
283 else if (a == ly_symbol2scm ("mean"))
284 // mean center distance
285 beam_dir = (total[UP] == total[DOWN]) ? neutral_dir
286 : (total[UP] > total[DOWN]) ? UP : DOWN;
287 else if (a == ly_symbol2scm ("median"))
289 // median center distance
290 if (count[DOWN] && count[UP])
292 beam_dir = (total[UP] / count[UP] == total[DOWN] / count[DOWN])
294 : (total[UP] / count[UP] > total[DOWN] / count[DOWN]) ? UP : DOWN;
298 beam_dir = (count[UP] == count[DOWN]) ? neutral_dir
299 : (count[UP] > count[DOWN]) ? UP : DOWN;
307 Beam::set_direction (Direction d)
309 Directional_spanner::set_direction (d);
310 for (int i=0; i <stem_count (); i++)
313 s->set_elt_property ("beam-dir", gh_int2scm (d));
315 SCM force = s->remove_elt_property ("dir-forced");
316 if (force == SCM_UNDEFINED)
317 s->set_direction ( d);
322 See Documentation/tex/fonts.doc
328 assert (stem_count () > 1);
331 Real x0 = stem (0)->hpos_f ();
332 for (int i=0; i < stem_count (); i++)
334 l.input.push (Offset (stem (i)->hpos_f () - x0,
335 stem (i)->get_real ("idealy-f")));
337 l.minimise (slope_f_, left_y_);
341 ugh. Naming: this doesn't check, but sets as well.
345 Beam::check_stemlengths_f (bool set_b)
347 Real interbeam_f = paper_l ()->interbeam_f (multiplicity_i_);
349 Real beam_f = gh_scm2double (get_elt_property ("beam-thickness"));
350 Real staffline_f = paper_l ()-> get_var ("stafflinethickness");
351 Real epsilon_f = staffline_f / 8;
353 Real x0 = stem (0)->hpos_f ();
354 Real internote_f = paper_l ()->get_var ("interline");
355 for (int i=0; i < stem_count (); i++)
357 Real y = (stem (i)->hpos_f () - x0) * slope_f_ + left_y_;
360 if (get_direction () != stem (i)->get_direction ())
362 y -= get_direction () * (beam_f / 2
363 + (multiplicity_i_ - 1) * interbeam_f);
365 && stem (i)->staff_symbol_l () != stem_top ()->staff_symbol_l ())
366 y += get_direction () * (multiplicity_i_ - (stem (i)->flag_i_ - 2) >? 0)
370 /* caution: stem measures in staff-positions */
372 stem (i)->set_stemend ((y - stem (i)->get_real ("interstaff-f"))
375 y *= get_direction ();
376 if (y > stem (i)->get_real ("maxy-f"))
377 dy_f = dy_f <? stem (i)->get_real ("maxy-f") - y;
378 if (y < stem (i)->get_real ("miny-f"))
380 // when all too short, normal stems win..
381 if (dy_f < -epsilon_f)
382 warning (_ ("weird beam vertical offset"));
383 dy_f = dy_f >? stem (i)->get_real ("miny-f") - y;
390 Beam::beamify_stems ()
395 assert (multiplicity_i_);
397 int total_count_i = 0;
398 int forced_count_i = 0;
399 for (int i=0; i < stem_count (); i++)
403 s->set_default_extents ();
404 if (s->invisible_b ())
406 if (((int)s->chord_start_f ()) && (s->get_direction () != s->get_default_dir ()))
411 Real internote_f = paper_l ()->get_var ("interline");
412 bool grace_b = get_elt_property ("grace") == SCM_BOOL_T;
413 String type_str = grace_b ? "grace_" : "";
414 int stem_max = (int)rint(paper_l ()->get_var ("stem_max"));
415 Real shorten_f = paper_l ()->get_var (type_str + "forced_stem_shorten"
416 + to_str (multiplicity_i_ <? stem_max)) * internote_f;
418 for (int i=0; i < stem_count (); i++)
422 Chord tremolo needs to beam over invisible stems of wholes
424 SCM trem = get_elt_property ("chord-tremolo");
425 if (gh_boolean_p (trem) && gh_scm2bool (trem))
427 if (s->invisible_b ())
432 if (s->get_direction () == get_direction ())
434 if (forced_count_i == total_count_i)
435 s->set_real ("idealy-f", s->get_real ("idealy-f") - shorten_f);
436 else if (forced_count_i > total_count_i / 2)
437 s->set_real ("idealy-f", s->get_real ("idealy-f") - shorten_f/2);
443 Beam::calculate_slope ()
446 slope_f_ = left_y_ = 0;
447 else if (stem (0)->get_real ("idealy-f") == stem_top ()->get_real ("idealy-f"))
450 left_y_ = stem (0)->get_real ("idealy-f");
451 left_y_ *= get_direction ();
456 Real solved_slope_f = slope_f_;
459 steep slope running against lengthened stem is suspect
461 Real dx_f = stem (stem_count () -1)->hpos_f () - stem (0)->hpos_f ();
463 Real lengthened = paper_l ()->get_var ("beam_lengthened");
464 Real steep = paper_l ()->get_var ("beam_steep_slope");
465 if (((left_y_ - stem (0)->get_real ("idealy-f") > lengthened)
466 && (slope_f_ > steep))
467 || ((left_y_ + slope_f_ * dx_f - stem_top ()->get_real ("idealy-f") > lengthened)
468 && (slope_f_ < -steep)))
474 This neat trick is by Werner Lemberg,
475 damped = tanh (slope_f_)
476 corresponds with some tables in [Wanske]
478 SCM damp = remove_elt_property ("damping");
479 int damping = 1; // ugh.
480 if (damp!= SCM_UNDEFINED)
481 damping = gh_int2scm (damp);
484 slope_f_ = 0.6 * tanh (slope_f_) / damping;
488 Real damped_slope_dy_f = (solved_slope_f - slope_f_) * dx_f / 2;
489 left_y_ += damped_slope_dy_f;
491 left_y_ *= get_direction ();
492 slope_f_ *= get_direction ();
500 [Ross] (simplification of)
501 Try to set slope_f_ complying with y-span of:
503 - beam_f / 2 + staffline_f / 2
504 - beam_f + staffline_f
508 SCM q = get_elt_property ("slope-quantisation");
510 if (q == ly_symbol2scm ("none"))
513 Real interline_f = stem (0)->staff_line_leading_f ();
514 Real staffline_f = paper_l ()->get_var ("stafflinethickness");
515 Real beam_f = gh_scm2double (get_elt_property ("beam-thickness"));;
517 Real dx_f = stem (stem_count () -1 )->hpos_f () - stem (0)->hpos_f ();
519 Real dy_f = dx_f * abs (slope_f_);
523 Array<Real> allowed_fraction (3);
524 allowed_fraction[0] = 0;
525 allowed_fraction[1] = (beam_f / 2 + staffline_f / 2);
526 allowed_fraction[2] = (beam_f + staffline_f);
528 Interval iv = quantise_iv (allowed_fraction, interline_f, dy_f);
529 quanty_f = (dy_f - iv[SMALLER] <= iv[BIGGER] - dy_f)
533 slope_f_ = (quanty_f / dx_f) * sign (slope_f_);
538 Prevent interference from stafflines and beams. See Documentation/tex/fonts.doc
542 Beam::quantise_left_y (bool extend_b)
545 we only need to quantise the start of the beam as dy is quantised too
546 if extend_b then stems must *not* get shorter
548 SCM q = get_elt_property ("slope-quantisation");
552 ----------------------------------------------------------
556 --------------########------------------------------------
559 hang straddle sit inter hang
562 Real space = stem (0)->staff_line_leading_f ();
563 Real staffline_f = paper_l ()->get_var ("stafflinethickness");
564 Real beam_f = gh_scm2double (get_elt_property ("beam-thickness"));;
568 it would be nice to have all allowed positions in a runtime matrix:
569 (multiplicity, minimum_beam_dy, maximum_beam_dy)
573 Real sit = beam_f / 2 - staffline_f / 2;
574 Real hang = space - beam_f / 2 + staffline_f / 2;
577 Put all allowed positions into an array.
578 Whether a position is allowed or not depends on
579 strictness of quantisation, multiplicity and direction.
581 For simplicity, we'll assume dir = UP and correct if
582 dir = DOWN afterwards.
584 // isn't this asymmetric ? --hwn
586 Real dy_f = get_direction () * left_y_;
588 Real beamdx_f = stem (stem_count () -1)->hpos_f () - stem (0)->hpos_f ();
589 Real beamdy_f = beamdx_f * slope_f_;
591 Array<Real> allowed_position;
592 if (q == ly_symbol2scm ("normal"))
594 if ((multiplicity_i_ <= 2) || (abs (beamdy_f) >= staffline_f / 2))
595 allowed_position.push (straddle);
596 if ((multiplicity_i_ <= 1) || (abs (beamdy_f) >= staffline_f / 2))
597 allowed_position.push (sit);
598 allowed_position.push (hang);
600 else if (q == ly_symbol2scm ("traditional"))
602 // TODO: check and fix TRADITIONAL
603 if ((multiplicity_i_ <= 2) || (abs (beamdy_f) >= staffline_f / 2))
604 allowed_position.push (straddle);
605 if ((multiplicity_i_ <= 1) && (beamdy_f <= staffline_f / 2))
606 allowed_position.push (sit);
607 if (beamdy_f >= -staffline_f / 2)
608 allowed_position.push (hang);
612 Interval iv = quantise_iv (allowed_position, space, dy_f);
614 Real quanty_f = dy_f - iv[SMALLER] <= iv[BIGGER] - dy_f ? iv[SMALLER] : iv[BIGGER];
616 quanty_f = iv[BIGGER];
618 left_y_ = get_direction () * quanty_f;
622 Beam::set_stemlens ()
624 Real staffline_f = paper_l ()->get_var ("stafflinethickness");
626 Real epsilon_f = staffline_f / 8;
629 // je bent zelf eng --hwn.
630 Real dy_f = check_stemlengths_f (false);
631 for (int i = 0; i < 2; i++) // 2 ?
633 left_y_ += dy_f * get_direction ();
634 quantise_left_y (dy_f);
635 dy_f = check_stemlengths_f (true);
636 if (abs (dy_f) <= epsilon_f)
644 Beam::set_beaming (Beaming_info_list *beaming)
647 for (int i=0; i < stem_count (); i++)
651 if (stem (i)->beams_i_drul_[d] < 0)
652 stem (i)->beams_i_drul_[d] = beaming->infos_.elem (i).beams_i_drul_[d];
654 while (flip (&d) != LEFT);
660 Beam::do_add_processing ()
662 for (int i=0; i < stem_count () ; i++)
666 multiplicity_i_ = multiplicity_i_ >? stem (i)->beams_i_drul_[d];
667 } while ((flip (&d)) != LEFT);
675 stem (0)->beams_i_drul_[LEFT] =0;
676 stem (stem_count () -1)->beams_i_drul_[RIGHT] =0;
683 beams to go with one stem.
688 Beam::stem_beams (Stem *here, Stem *next, Stem *prev) const
690 if ((next && !(next->hpos_f () > here->hpos_f ())) ||
691 (prev && !(prev->hpos_f () < here->hpos_f ())))
692 programming_error ("Beams are not left-to-right");
694 Real staffline_f = paper_l ()->get_var ("stafflinethickness");
695 Real interbeam_f = paper_l ()->interbeam_f (multiplicity_i_);
696 Real beam_f = gh_scm2double (get_elt_property ("beam-thickness"));;
698 Real dy = interbeam_f;
699 Real stemdx = staffline_f;
707 if (!here->first_head ())
709 else if (here->type_i ()== 1)
710 nw_f = paper_l ()->get_var ("wholewidth");
711 else if (here->type_i () == 2)
712 nw_f = paper_l ()->get_var ("notewidth") * 0.8;
714 nw_f = paper_l ()->get_var ("quartwidth");
716 /* half beams extending to the left. */
719 int lhalfs= lhalfs = here->beams_i_drul_[LEFT] - prev->beams_i_drul_[RIGHT] ;
720 int lwholebeams= here->beams_i_drul_[LEFT] <? prev->beams_i_drul_[RIGHT] ;
722 Half beam should be one note-width,
723 but let's make sure two half-beams never touch
725 Real w = here->hpos_f () - prev->hpos_f ();
728 if (lhalfs) // generates warnings if not
729 a = lookup_l ()->beam (sl, w, beam_f);
730 a.translate (Offset (-w, -w * sl));
731 for (int j = 0; j < lhalfs; j++)
734 b.translate_axis (-get_direction () * dy * (lwholebeams+j), Y_AXIS);
735 leftbeams.add_molecule (b);
741 int rhalfs = here->beams_i_drul_[RIGHT] - next->beams_i_drul_[LEFT];
742 int rwholebeams = here->beams_i_drul_[RIGHT] <? next->beams_i_drul_[LEFT];
744 Real w = next->hpos_f () - here->hpos_f ();
745 Molecule a = lookup_l ()->beam (sl, w + stemdx, beam_f);
746 a.translate_axis( - stemdx/2, X_AXIS);
750 SCM gap = get_elt_property ("beam-gap");
751 if (gap != SCM_UNDEFINED)
753 int gap_i = gh_scm2int ( (gap));
754 int nogap = rwholebeams - gap_i;
756 for (; j < nogap; j++)
759 b.translate_axis (-get_direction () * dy * j, Y_AXIS);
760 rightbeams.add_molecule (b);
762 // TODO: notehead widths differ for different types
765 a = lookup_l ()->beam (sl, w + stemdx, beam_f);
768 for (; j < rwholebeams; j++)
771 if (!here->invisible_b ())
772 b.translate (Offset (gap_f, -get_direction () * dy * j));
774 b.translate (Offset (0, -get_direction () * dy * j));
775 rightbeams.add_molecule (b);
780 a = lookup_l ()->beam (sl, w, beam_f);
782 for (; j < rwholebeams + rhalfs; j++)
785 b.translate_axis (-get_direction () * dy * j, Y_AXIS);
786 rightbeams.add_molecule (b);
790 leftbeams.add_molecule (rightbeams);
793 Does beam quanting think of the asymetry of beams?
794 Refpoint is on bottom of symbol. (FIXTHAT) --hwn.