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 return Pointer_group_interface::count (me, "heads");
157 The note head which forms one end of the stem.
160 Stem::first_head (Score_element*me)
162 return extremal_heads (me)[-get_direction (me)];
166 START is part where stem reaches `last' head.
168 Drul_array<Score_element*>
169 Stem::extremal_heads (Score_element*me)
171 const int inf = 1000000;
172 Drul_array<int> extpos;
176 Drul_array<Score_element *> exthead;
177 exthead[LEFT] = exthead[RIGHT] =0;
179 for (SCM s = me->get_elt_property ("heads"); gh_pair_p (s); s = gh_cdr (s))
181 Score_element * n = unsmob_element (gh_car (s));
184 int p = int(Staff_symbol_referencer::position_f (n));
188 if (d* p > d* extpos[d])
193 } while (flip (&d) != DOWN);
200 Stem::add_head (Score_element*me, Score_element *n)
202 n->set_elt_property ("stem", me->self_scm ());
203 n->add_dependency (me);
205 if (Note_head::has_interface (n))
207 Pointer_group_interface::add_element (me, "heads",n);
211 n->set_elt_property ("rest", n->self_scm ());
216 Stem::invisible_b (Score_element*me)
218 return !(heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
222 Stem::get_center_distance (Score_element*me, Direction d)
224 int staff_center = 0;
225 int distance = (int) (d*(head_positions(me)[d] - staff_center));
226 return distance >? 0;
230 Stem::get_default_dir (Score_element*me)
232 int du = get_center_distance (me,UP);
233 int dd = get_center_distance (me,DOWN);
236 return Direction (sign (dd -du));
238 return to_dir (me->get_elt_property ("default-neutral-direction"));
242 ugh. A is used for different purposes. This functionality should be
243 moved into scheme at some point to get rid of the silly
244 conversions. (but lets wait till we have namespaces in SCM)
247 Stem::get_default_stem_end_position (Score_element*me)
249 bool grace_b = to_boolean (me->get_elt_property ("grace"));
250 String type_str = grace_b ? "grace-" : "";
255 SCM scm_len = me->get_elt_property("length");
256 if (gh_number_p (scm_len))
258 length_f = gh_scm2double (scm_len);
262 s = me->get_elt_property("lengths");
263 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
264 a.push (gh_scm2double (gh_car (q)));
266 // stem uses half-spaces
267 length_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
272 s = me->get_elt_property ("stem-shorten");
273 for (SCM q = s; gh_pair_p (q); q = gh_cdr (q))
274 a.push (gh_scm2double (gh_car (q)));
277 // stem uses half-spaces
279 // fixme: use gh_list_ref () iso. array[]
280 Real shorten_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
283 'set-default-stemlen' sets direction too
285 Direction dir = get_direction (me);
288 dir = get_default_dir (me);
289 Directional_element_interface::set (me, dir);
293 stems in unnatural (forced) direction should be shortened,
294 according to [Roush & Gourlay]
296 if (((int)chord_start_f (me))
297 && (get_direction (me) != get_default_dir (me)))
298 length_f -= shorten_f;
301 Real st = head_positions(me)[dir] + dir * length_f;
303 bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
304 if (!grace_b && !no_extend_b && dir * st < 0)
314 Stem::flag_i (Score_element*me)
316 SCM s = me->get_elt_property ("duration-log");
317 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
321 Stem::position_noteheads (Score_element*me)
326 Link_array<Score_element> heads =
327 Pointer_group_interface__extract_elements (me, (Score_element*)0, "heads");
329 heads.sort (compare_position);
330 Direction dir =get_direction (me);
336 Score_element *hed = support_head (me);
337 Real w = hed->extent (hed, X_AXIS)[dir];
338 for (int i=0; i < heads.size (); i++)
340 heads[i]->translate_axis (w - heads[i]->extent (heads[i], 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 (heads[i], 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 (scm_sloppy_memq (scmdir, dirlist) == SCM_BOOL_F)
405 dirlist = gh_cons (scmdir, dirlist);
406 col->set_elt_property ("dir-list", dirlist);
412 Stem::flag (Score_element*me)
415 SCM st = me->get_elt_property ("flag-style");
416 if ( gh_string_p (st))
418 style = ly_scm2string (st);
421 char c = (get_direction (me) == UP) ? 'u' : 'd';
422 Molecule m = me->lookup_l ()->afm_find (String ("flags-") + to_str (c) +
423 to_str (flag_i (me)));
424 if (!style.empty_b ())
425 m.add_molecule(me->lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
429 MAKE_SCHEME_CALLBACK(Stem,dim_callback,2);
431 Stem::dim_callback (SCM e, SCM )
433 Score_element *se = unsmob_element (e);
435 if (unsmob_element (se->get_elt_property ("beam")) || abs (flag_i (se)) <= 2)
439 r = flag (se).extent (X_AXIS);
441 return ly_interval2scm ( r);
445 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
448 MAKE_SCHEME_CALLBACK(Stem,brew_molecule,1);
451 Stem::brew_molecule (SCM smob)
453 Score_element*me = unsmob_element (smob);
455 Direction d = get_direction (me);
458 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
459 Real y2 = stem_end_position (me);
461 Interval stem_y(y1,y2);
462 stem_y.unite (Interval (y2,y1));
464 Real dy = Staff_symbol_referencer::staff_space (me)/2.0;
467 if (Score_element *hed = support_head (me))
468 head_wid = hed->extent (hed,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 )
493 Score_element *me = unsmob_element (element_smob);
496 if (Score_element * f = first_head (me))
498 Interval head_wid(0, f->extent (f,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_interface (ly_symbol2scm ("stem-interface"));