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,1);
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,1);
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();
489 MAKE_SCHEME_CALLBACK(Stem,off_callback,2);
491 Stem::off_callback (SCM element_smob, SCM axis)
493 Score_element *me = unsmob_element (element_smob);
494 Axis a = (Axis) gh_scm2int (axis);
496 if (Score_element * f = first_head (me))
498 Interval head_wid(0, f->extent (X_AXIS).length ());
500 if (to_boolean (me->get_elt_property ("stem-centered")))
501 return gh_double2scm ( head_wid.center ());
503 Real rule_thick = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
504 Direction d = get_direction (me);
505 r = head_wid[d] - d * rule_thick ;
507 return gh_double2scm (r);
513 Stem::beam_l (Score_element*me)
515 SCM b= me->get_elt_property ("beam");
516 return unsmob_element (b);
520 // ugh still very long.
522 Stem::calc_stem_info (Score_element*me)
524 Score_element * beam = beam_l (me);
526 Direction beam_dir = Directional_element_interface::get (beam);
529 programming_error ("Beam dir not set.");
534 Real staff_space = Staff_symbol_referencer::staff_space (me);
535 Real half_space = staff_space / 2;
536 int multiplicity = Beam::get_multiplicity (beam);
539 SCM space_proc = beam->get_elt_property ("space-function");
540 SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
541 Real interbeam_f = gh_scm2double (space) * staff_space;
543 Real thick = gh_scm2double (beam->get_elt_property ("thickness"));
545 info.idealy_f_ = chord_start_f (me);
547 // for simplicity, we calculate as if dir == UP
548 info.idealy_f_ *= beam_dir;
549 SCM grace_prop = me->get_elt_property ("grace");
551 bool grace_b = to_boolean (grace_prop);
556 s = me->get_elt_property("beamed-minimum-lengths");
558 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
559 a.push (gh_scm2double (gh_car (q)));
562 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
563 s = me->get_elt_property ("beamed-lengths");
566 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
567 a.push (gh_scm2double (gh_car (q)));
569 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
571 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
572 /* normal beamed stem */
576 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
578 info.miny_f_ = info.idealy_f_;
579 info.maxy_f_ = INT_MAX;
581 info.idealy_f_ += stem_length;
582 info.miny_f_ += minimum_length;
585 lowest beam of (UP) beam must never be lower than second staffline
587 Hmm, reference (Wanske?)
589 Although this (additional) rule is probably correct,
590 I expect that highest beam (UP) should also never be lower
591 than middle staffline, just as normal stems.
594 bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
595 if (!grace_b && !no_extend_b)
597 /* highest beam of (UP) beam must never be lower than middle
599 lowest beam of (UP) beam must never be lower than second staffline
603 >? (- 2 * half_space - thick
604 + (multiplicity > 0) * thick
605 + interbeam_f * (multiplicity - 1));
611 info.idealy_f_ -= thick;
612 info.maxy_f_ = info.idealy_f_;
613 info.miny_f_ = -INT_MAX;
615 info.idealy_f_ -= stem_length;
616 info.maxy_f_ -= minimum_length;
619 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
621 s = beam->get_elt_property ("shorten");
623 info.idealy_f_ -= gh_scm2double (s);
625 Real interstaff_f = -beam_dir* calc_interstaff_dist (dynamic_cast<Item*> (me), dynamic_cast<Spanner*> (beam));
627 info.idealy_f_ += interstaff_f;
628 info.miny_f_ += interstaff_f;
629 info.maxy_f_ += interstaff_f ;
635 Stem::has_interface (Score_element*m)
637 return m && m->has_interface (ly_symbol2scm ("stem-interface"));
641 Stem::set_interface (Score_element*me)
643 me->set_elt_property ("heads", SCM_EOL);
644 me->add_offset_callback ( Stem_off_callback_proc, X_AXIS);
645 me->set_interface (ly_symbol2scm ("stem-interface"));