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));
432 MAKE_SCHEME_CALLBACK(Stem,dim_callback,2);
434 Stem::dim_callback (SCM e, SCM )
436 Score_element *se = unsmob_element (e);
438 if (unsmob_element (se->get_elt_property ("beam")) || abs (flag_i (se)) <= 2)
442 r = flag (se).extent (X_AXIS);
444 return ly_interval2scm ( r);
448 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
451 MAKE_SCHEME_CALLBACK(Stem,brew_molecule,1);
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 = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
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();
491 MAKE_SCHEME_CALLBACK(Stem,off_callback,2);
493 Stem::off_callback (SCM element_smob, SCM axis)
495 Score_element *me = unsmob_element (element_smob);
498 if (Score_element * f = first_head (me))
500 Interval head_wid(0, f->extent (X_AXIS).length ());
502 if (to_boolean (me->get_elt_property ("stem-centered")))
503 return gh_double2scm ( head_wid.center ());
505 Real rule_thick = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
506 Direction d = get_direction (me);
507 r = head_wid[d] - d * rule_thick ;
509 return gh_double2scm (r);
515 Stem::beam_l (Score_element*me)
517 SCM b= me->get_elt_property ("beam");
518 return unsmob_element (b);
522 // ugh still very long.
524 Stem::calc_stem_info (Score_element*me)
526 Score_element * beam = beam_l (me);
528 Direction beam_dir = Directional_element_interface::get (beam);
531 programming_error ("Beam dir not set.");
536 Real staff_space = Staff_symbol_referencer::staff_space (me);
537 Real half_space = staff_space / 2;
538 int multiplicity = Beam::get_multiplicity (beam);
541 SCM space_proc = beam->get_elt_property ("space-function");
542 SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
543 Real interbeam_f = gh_scm2double (space) * staff_space;
545 Real thick = gh_scm2double (beam->get_elt_property ("thickness"));
547 info.idealy_f_ = chord_start_f (me);
549 // for simplicity, we calculate as if dir == UP
550 info.idealy_f_ *= beam_dir;
551 SCM grace_prop = me->get_elt_property ("grace");
553 bool grace_b = to_boolean (grace_prop);
558 s = me->get_elt_property("beamed-minimum-lengths");
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 = me->get_elt_property ("beamed-lengths");
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_interface::get (me)))
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 (me->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->get_elt_property ("shorten");
625 info.idealy_f_ -= gh_scm2double (s);
627 Real interstaff_f = -beam_dir* calc_interstaff_dist (dynamic_cast<Item*> (me), dynamic_cast<Spanner*> (beam));
629 info.idealy_f_ += interstaff_f;
630 info.miny_f_ += interstaff_f;
631 info.maxy_f_ += interstaff_f ;
637 Stem::has_interface (Score_element*m)
639 return m && m->has_interface (ly_symbol2scm ("stem-interface"));
643 Stem::set_interface (Score_element*me)
645 me->set_interface (ly_symbol2scm ("stem-interface"));