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 = scm_eval2 (ly_symbol2scm ((type_str + "stem-length").ch_C()),
265 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
266 a.push (gh_scm2double (gh_car (q)));
268 // stem uses half-spaces
269 length_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
274 s = scm_eval2 (ly_symbol2scm ((type_str + "stem-shorten").ch_C()),
276 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
277 a.push (gh_scm2double (gh_car (q)));
280 // stem uses half-spaces
282 // fixme: use gh_list_ref () iso. array[]
283 Real shorten_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
286 'set-default-stemlen' sets direction too
288 Direction dir = get_direction (me);
291 dir = get_default_dir (me);
292 Directional_element_interface::set (me, dir);
296 stems in unnatural (forced) direction should be shortened,
297 according to [Roush & Gourlay]
299 if (((int)chord_start_f (me))
300 && (get_direction (me) != get_default_dir (me)))
301 length_f -= shorten_f;
304 Real st = head_positions(me)[dir] + dir * length_f;
306 bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
307 if (!grace_b && !no_extend_b && dir * st < 0)
317 Stem::flag_i (Score_element*me)
319 SCM s = me->get_elt_property ("duration-log");
320 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
324 Stem::position_noteheads (Score_element*me)
329 Link_array<Score_element> heads =
330 Pointer_group_interface__extract_elements (me, (Score_element*)0, "heads");
332 heads.sort (compare_position);
333 Direction dir =get_direction (me);
339 Real w = support_head (me)->extent (X_AXIS)[dir];
340 for (int i=0; i < heads.size (); i++)
342 heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
345 bool parity= true; // todo: make me settable.
346 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
347 for (int i=1; i < heads.size (); i ++)
349 Real p = Staff_symbol_referencer::position_f (heads[i]);
350 int dy =abs (lastpos- (int)p);
356 Real l = heads[i]->extent (X_AXIS).length ();
357 heads[i]->translate_axis (l * get_direction (me), X_AXIS);
368 MAKE_SCHEME_CALLBACK(Stem,before_line_breaking);
370 Stem::before_line_breaking (SCM smob)
372 Score_element*me = unsmob_element (smob);
373 stem_end_position (me); // ugh. Trigger direction calc.
374 position_noteheads (me);
376 if (invisible_b (me))
378 me->remove_elt_property ("molecule-callback");
382 set_spacing_hints (me);
383 return SCM_UNSPECIFIED;
389 set stem directions for hinting the optical spacing correction.
391 Modifies DIR_LIST property of the Stem's Paper_column
393 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
396 Stem::set_spacing_hints (Score_element*me)
398 if (!invisible_b (me))
400 SCM scmdir = gh_int2scm (get_direction (me));
402 Item* item = dynamic_cast<Item*> (me);
403 Item * col = item->column_l ();
404 SCM dirlist =col->get_elt_property ("dir-list");
405 if (dirlist == SCM_UNDEFINED)
408 if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
410 dirlist = gh_cons (scmdir, dirlist);
411 col->set_elt_property ("dir-list", dirlist);
417 Stem::flag (Score_element*me)
420 SCM st = me->get_elt_property ("flag-style");
421 if ( gh_string_p (st))
423 style = ly_scm2string (st);
426 char c = (get_direction (me) == UP) ? 'u' : 'd';
427 Molecule m = me->lookup_l ()->afm_find (String ("flags-") + to_str (c) +
428 to_str (flag_i (me)));
429 if (!style.empty_b ())
430 m.add_molecule(me->lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
435 Stem::dim_callback (Score_element *se, Axis )
438 if (unsmob_element (se->get_elt_property ("beam")) || abs (flag_i (se)) <= 2)
442 r = flag (se).extent (X_AXIS);
448 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
451 MAKE_SCHEME_CALLBACK(Stem,brew_molecule);
454 Stem::brew_molecule (SCM smob)
456 Score_element*me = unsmob_element (smob);
458 Direction d = get_direction (me);
461 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
462 Real y2 = stem_end_position (me);
464 Interval stem_y(y1,y2);
465 stem_y.unite (Interval (y2,y1));
467 Real dy = Staff_symbol_referencer::staff_space (me)/2.0;
469 if (support_head (me))
470 head_wid = support_head (me)->extent (X_AXIS).length ();
471 stem_y[Direction(-d)] += d * head_wid * tan(ANGLE)/(2*dy);
473 if (!invisible_b (me))
475 Real stem_width = me->paper_l ()->get_var ("stemthickness");
476 Molecule ss =me->lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
477 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
478 mol.add_molecule (ss);
481 if (!beam_l (me) && abs (flag_i (me)) > 2)
483 Molecule fl = flag (me);
484 fl.translate_axis(stem_y[d]*dy, Y_AXIS);
485 mol.add_molecule (fl);
488 return mol.create_scheme();
492 Stem::off_callback (Score_element * me, Axis)
495 if (Score_element * f = first_head (me))
497 Interval head_wid(0, f->extent (X_AXIS).length ());
499 if (to_boolean (me->get_elt_property ("stem-centered")))
500 return head_wid.center ();
502 Real rule_thick = me->paper_l ()->get_var ("stemthickness");
503 Direction d = get_direction (me);
504 r = head_wid[d] - d * rule_thick ;
512 Stem::beam_l (Score_element*me)
514 SCM b= me->get_elt_property ("beam");
515 return unsmob_element (b);
519 // ugh still very long.
521 Stem::calc_stem_info (Score_element*me)
523 Score_element * beam = beam_l (me);
525 Direction beam_dir = Directional_element_interface::get (beam);
528 programming_error ("Beam dir not set.");
533 Real staff_space = Staff_symbol_referencer::staff_space (me);
534 Real half_space = staff_space / 2;
535 Real interbeam_f = me->paper_l ()->interbeam_f (Beam::get_multiplicity (beam));
536 Real thick = gh_scm2double (beam->get_elt_property ("beam-thickness"));
537 int multiplicity = Beam::get_multiplicity (beam);
540 info.idealy_f_ = chord_start_f (me);
542 // for simplicity, we calculate as if dir == UP
543 info.idealy_f_ *= beam_dir;
544 SCM grace_prop = me->get_elt_property ("grace");
546 bool grace_b = to_boolean (grace_prop);
550 String type_str = grace_b ? "grace-" : "";
552 s = scm_eval2 (ly_symbol2scm ((type_str + "beamed-stem-minimum-length").ch_C()), SCM_EOL);
554 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
555 a.push (gh_scm2double (gh_car (q)));
558 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
559 s = scm_eval2 (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()),
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"));