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 "note-head.hh"
17 #include "paper-def.hh"
18 #include "rhythmic-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 (Score_element*me ,int i, Direction d )
34 SCM pair = me->get_elt_property ("beaming");
36 if (!gh_pair_p (pair))
38 pair = gh_cons (gh_int2scm (0),gh_int2scm (0));
39 me-> set_elt_property ("beaming", pair);
41 index_set_cell (pair, d, gh_int2scm (i));
45 Stem::beam_count (Score_element*me,Direction d)
47 SCM p=me->get_elt_property ("beaming");
49 return gh_scm2int (index_cell (p,d));
55 Stem::head_positions (Score_element*me)
63 Drul_array<Score_element*> e (extremal_heads (me));
65 return Interval (Staff_symbol_referencer::position_f (e[DOWN]),
66 Staff_symbol_referencer::position_f ( e[UP]));
71 Stem::chord_start_f (Score_element*me)
73 return head_positions(me)[get_direction (me)]
74 * Staff_symbol_referencer::staff_space (me)/2.0;
78 Stem::stem_end_position (Score_element*me)
80 SCM p =me->get_elt_property ("stem-end-position");
85 pos = get_default_stem_end_position (me);
86 me->set_elt_property ("stem-end-position", gh_double2scm (pos));
89 pos = gh_scm2double (p);
95 Stem::get_direction (Score_element*me)
97 Direction d = Directional_element_interface::get (me);
101 d = get_default_dir (me);
103 Directional_element_interface::set (me, d);
110 Stem::set_stemend (Score_element*me, Real se)
113 Direction d= get_direction (me);
115 if (d && d * head_positions(me)[get_direction (me)] >= se*d)
116 warning (_ ("Weird stem size; check for narrow beams"));
118 me->set_elt_property ("stem-end-position", gh_double2scm (se));
122 Stem::type_i (Score_element*me)
124 return first_head (me) ? Rhythmic_head::balltype_i (first_head (me)) : 2;
128 Note head that determines hshift for upstems
131 Stem::support_head (Score_element*me)
133 SCM h = me->get_elt_property ("support-head");
134 Score_element * nh = unsmob_element (h);
137 else if (heads_i (me) == 1)
143 return unsmob_element (gh_car (me->get_elt_property ("heads")));
146 return first_head (me);
151 Stem::heads_i (Score_element*me)
153 Pointer_group_interface gi (me, "heads");
158 The note head which forms one end of the stem.
161 Stem::first_head (Score_element*me)
163 return extremal_heads (me)[-get_direction (me)];
167 START is part where stem reaches `last' head.
169 Drul_array<Score_element*>
170 Stem::extremal_heads (Score_element*me)
172 const int inf = 1000000;
173 Drul_array<int> extpos;
177 Drul_array<Score_element *> exthead;
178 exthead[LEFT] = exthead[RIGHT] =0;
180 for (SCM s = me->get_elt_property ("heads"); gh_pair_p (s); s = gh_cdr (s))
182 Score_element * n = unsmob_element (gh_car (s));
185 int p = int(Staff_symbol_referencer::position_f (n));
189 if (d* p > d* extpos[d])
194 } while (flip (&d) != DOWN);
201 Stem::add_head (Score_element*me, Score_element *n)
203 n->set_elt_property ("stem", me->self_scm ());
204 n->add_dependency (me);
206 if (Note_head::has_interface (n))
208 Pointer_group_interface (me, "heads").add_element (n);
212 n->set_elt_property ("rest", n->self_scm ());
217 Stem::invisible_b (Score_element*me)
219 return !(heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
223 Stem::get_center_distance (Score_element*me, Direction d)
225 int staff_center = 0;
226 int distance = (int) (d*(head_positions(me)[d] - staff_center));
227 return distance >? 0;
231 Stem::get_default_dir (Score_element*me)
233 int du = get_center_distance (me,UP);
234 int dd = get_center_distance (me,DOWN);
237 return Direction (sign (dd -du));
239 return to_dir (me->get_elt_property ("default-neutral-direction"));
243 ugh. A is used for different purposes. This functionality should be
244 moved into scheme at some point to get rid of the silly
245 conversions. (but lets wait till we have namespaces in SCM)
248 Stem::get_default_stem_end_position (Score_element*me)
250 bool grace_b = to_boolean (me->get_elt_property ("grace"));
251 String type_str = grace_b ? "grace-" : "";
256 SCM scm_len = me->get_elt_property("length");
257 if (gh_number_p (scm_len))
259 length_f = gh_scm2double (scm_len);
263 s = me->get_elt_property("lengths");
264 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
265 a.push (gh_scm2double (gh_car (q)));
267 // stem uses half-spaces
268 length_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
273 s = me->get_elt_property ("stem-shorten");
274 for (SCM q = s; gh_pair_p (q); q = gh_cdr (q))
275 a.push (gh_scm2double (gh_car (q)));
278 // stem uses half-spaces
280 // fixme: use gh_list_ref () iso. array[]
281 Real shorten_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
284 'set-default-stemlen' sets direction too
286 Direction dir = get_direction (me);
289 dir = get_default_dir (me);
290 Directional_element_interface::set (me, dir);
294 stems in unnatural (forced) direction should be shortened,
295 according to [Roush & Gourlay]
297 if (((int)chord_start_f (me))
298 && (get_direction (me) != get_default_dir (me)))
299 length_f -= shorten_f;
302 Real st = head_positions(me)[dir] + dir * length_f;
304 bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
305 if (!grace_b && !no_extend_b && dir * st < 0)
315 Stem::flag_i (Score_element*me)
317 SCM s = me->get_elt_property ("duration-log");
318 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
322 Stem::position_noteheads (Score_element*me)
327 Link_array<Score_element> heads =
328 Pointer_group_interface__extract_elements (me, (Score_element*)0, "heads");
330 heads.sort (compare_position);
331 Direction dir =get_direction (me);
337 Real w = support_head (me)->extent (X_AXIS)[dir];
338 for (int i=0; i < heads.size (); i++)
340 heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
343 bool parity= true; // todo: make me settable.
344 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
345 for (int i=1; i < heads.size (); i ++)
347 Real p = Staff_symbol_referencer::position_f (heads[i]);
348 int dy =abs (lastpos- (int)p);
354 Real l = heads[i]->extent (X_AXIS).length ();
355 heads[i]->translate_axis (l * get_direction (me), X_AXIS);
366 MAKE_SCHEME_CALLBACK(Stem,before_line_breaking);
368 Stem::before_line_breaking (SCM smob)
370 Score_element*me = unsmob_element (smob);
371 stem_end_position (me); // ugh. Trigger direction calc.
372 position_noteheads (me);
374 if (invisible_b (me))
376 me->remove_elt_property ("molecule-callback");
380 set_spacing_hints (me);
381 return SCM_UNSPECIFIED;
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 (Score_element*me)
396 if (!invisible_b (me))
398 SCM scmdir = gh_int2scm (get_direction (me));
400 Item* item = dynamic_cast<Item*> (me);
401 Item * col = item->column_l ();
402 SCM dirlist =col->get_elt_property ("dir-list");
403 if (dirlist == SCM_UNDEFINED)
406 if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
408 dirlist = gh_cons (scmdir, dirlist);
409 col->set_elt_property ("dir-list", dirlist);
415 Stem::flag (Score_element*me)
418 SCM st = me->get_elt_property ("flag-style");
419 if ( gh_string_p (st))
421 style = ly_scm2string (st);
424 char c = (get_direction (me) == UP) ? 'u' : 'd';
425 Molecule m = me->lookup_l ()->afm_find (String ("flags-") + to_str (c) +
426 to_str (flag_i (me)));
427 if (!style.empty_b ())
428 m.add_molecule(me->lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
433 Stem::dim_callback (Score_element *se, Axis )
436 if (unsmob_element (se->get_elt_property ("beam")) || abs (flag_i (se)) <= 2)
440 r = flag (se).extent (X_AXIS);
446 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
449 MAKE_SCHEME_CALLBACK(Stem,brew_molecule);
452 Stem::brew_molecule (SCM smob)
454 Score_element*me = unsmob_element (smob);
456 Direction d = get_direction (me);
459 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
460 Real y2 = stem_end_position (me);
462 Interval stem_y(y1,y2);
463 stem_y.unite (Interval (y2,y1));
465 Real dy = Staff_symbol_referencer::staff_space (me)/2.0;
467 if (support_head (me))
468 head_wid = support_head (me)->extent (X_AXIS).length ();
469 stem_y[Direction(-d)] += d * head_wid * tan(ANGLE)/(2*dy);
471 if (!invisible_b (me))
473 Real stem_width = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
474 Molecule ss =me->lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
475 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
476 mol.add_molecule (ss);
479 if (!beam_l (me) && abs (flag_i (me)) > 2)
481 Molecule fl = flag (me);
482 fl.translate_axis(stem_y[d]*dy, Y_AXIS);
483 mol.add_molecule (fl);
486 return mol.create_scheme();
490 Stem::off_callback (Score_element * me, Axis)
493 if (Score_element * f = first_head (me))
495 Interval head_wid(0, f->extent (X_AXIS).length ());
497 if (to_boolean (me->get_elt_property ("stem-centered")))
498 return head_wid.center ();
500 Real rule_thick = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
501 Direction d = get_direction (me);
502 r = head_wid[d] - d * rule_thick ;
510 Stem::beam_l (Score_element*me)
512 SCM b= me->get_elt_property ("beam");
513 return unsmob_element (b);
517 // ugh still very long.
519 Stem::calc_stem_info (Score_element*me)
521 Score_element * beam = beam_l (me);
523 Direction beam_dir = Directional_element_interface::get (beam);
526 programming_error ("Beam dir not set.");
531 Real staff_space = Staff_symbol_referencer::staff_space (me);
532 Real half_space = staff_space / 2;
533 int multiplicity = Beam::get_multiplicity (beam);
536 SCM space_proc = beam->get_elt_property ("space-function");
537 SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
538 Real interbeam_f = gh_scm2double (space) * staff_space;
540 Real thick = gh_scm2double (beam->get_elt_property ("thickness"));
542 info.idealy_f_ = chord_start_f (me);
544 // for simplicity, we calculate as if dir == UP
545 info.idealy_f_ *= beam_dir;
546 SCM grace_prop = me->get_elt_property ("grace");
548 bool grace_b = to_boolean (grace_prop);
553 s = me->get_elt_property("beamed-minimum-lengths");
555 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
556 a.push (gh_scm2double (gh_car (q)));
559 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
560 s = me->get_elt_property ("beamed-lengths");
563 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
564 a.push (gh_scm2double (gh_car (q)));
566 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
568 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
569 /* normal beamed stem */
573 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
575 info.miny_f_ = info.idealy_f_;
576 info.maxy_f_ = INT_MAX;
578 info.idealy_f_ += stem_length;
579 info.miny_f_ += minimum_length;
582 lowest beam of (UP) beam must never be lower than second staffline
584 Hmm, reference (Wanske?)
586 Although this (additional) rule is probably correct,
587 I expect that highest beam (UP) should also never be lower
588 than middle staffline, just as normal stems.
591 bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
592 if (!grace_b && !no_extend_b)
594 /* highest beam of (UP) beam must never be lower than middle
596 lowest beam of (UP) beam must never be lower than second staffline
600 >? (- 2 * half_space - thick
601 + (multiplicity > 0) * thick
602 + interbeam_f * (multiplicity - 1));
608 info.idealy_f_ -= thick;
609 info.maxy_f_ = info.idealy_f_;
610 info.miny_f_ = -INT_MAX;
612 info.idealy_f_ -= stem_length;
613 info.maxy_f_ -= minimum_length;
616 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
618 s = beam->get_elt_property ("shorten");
620 info.idealy_f_ -= gh_scm2double (s);
622 Real interstaff_f = -beam_dir* calc_interstaff_dist (dynamic_cast<Item*> (me), dynamic_cast<Spanner*> (beam));
624 info.idealy_f_ += interstaff_f;
625 info.miny_f_ += interstaff_f;
626 info.maxy_f_ += interstaff_f ;
632 Stem::has_interface (Score_element*m)
634 return m && m->has_interface (ly_symbol2scm ("stem-interface"));
638 Stem::set_interface (Score_element*me)
640 me->set_elt_property ("heads", SCM_EOL);
641 me->add_offset_callback ( &Stem::off_callback, X_AXIS);
642 me->set_interface (ly_symbol2scm ("stem-interface"));