lilypond-1.3.66
[lilypond.git] / lily / stem.cc
blobe8fe8b4647e08653252add2ce91dceb65a097141
1 /*
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 "dimension-cache.hh"
15 #include "stem.hh"
16 #include "debug.hh"
17 #include "paper-def.hh"
18 #include "note-head.hh"
19 #include "lookup.hh"
20 #include "molecule.hh"
21 #include "paper-column.hh"
22 #include "misc.hh"
23 #include "beam.hh"
24 #include "rest.hh"
25 #include "group-interface.hh"
26 #include "cross-staff.hh"
27 #include "staff-symbol-referencer.hh"
31 void
32 Stem::set_beaming (int i, Direction d )
34 SCM pair = get_elt_property ("beaming");
36 if (!gh_pair_p (pair))
38 pair = gh_cons (gh_int2scm (0),gh_int2scm (0));
39 set_elt_property ("beaming", pair);
41 index_set_cell (pair, d, gh_int2scm (i));
44 int
45 Stem::beam_count (Direction d) const
47 SCM p=get_elt_property ("beaming");
48 if (gh_pair_p (p))
49 return gh_scm2int (index_cell (p,d));
50 else
51 return 0;
54 Interval
55 Stem::head_positions () const
57 if (!heads_i ())
59 Interval iv;
60 return iv;
64 Drul_array<Note_head*> e (extremal_heads ());
66 return Interval (staff_symbol_referencer (e[DOWN]).position_f (),
67 staff_symbol_referencer( e[UP]).position_f ());
71 Real
72 Stem::chord_start_f () const
74 return head_positions()[get_direction ()]
75 * Staff_symbol_referencer_interface (this).staff_space ()/2.0;
78 Real
79 Stem::stem_end_position () const
81 SCM p =get_elt_property ("stem-end-position");
82 Real pos;
83 if (!gh_number_p (p))
85 Stem * me = (Stem*) this;
86 pos = get_default_stem_end_position ();
87 me->set_elt_property ("stem-end-position", gh_double2scm (pos));
89 else
90 pos = gh_scm2double (p);
92 return pos;
95 Direction
96 Stem::get_direction () const
98 Direction d = Directional_element_interface (this).get ();
100 if (!d)
102 Stem * me = (Stem*) this;
103 d = get_default_dir ();
104 // urg, AAARGH!
105 Directional_element_interface (me).set (d);
107 return d ;
111 void
112 Stem::set_stemend (Real se)
114 // todo: margins
115 Direction d= get_direction ();
117 if (d && d * head_positions()[get_direction ()] >= se*d)
118 warning (_ ("Weird stem size; check for narrow beams"));
120 set_elt_property ("stem-end-position", gh_double2scm (se));
124 Stem::type_i () const
126 return first_head () ? first_head ()->balltype_i () : 2;
130 Note head that determines hshift for upstems
132 Score_element*
133 Stem::support_head ()const
135 SCM h = get_elt_pointer ("support-head");
136 Score_element * nh = unsmob_element (h);
137 if (nh)
138 return nh;
139 else if (heads_i () == 1)
142 UGH.
145 return unsmob_element (gh_car (get_elt_pointer ("heads")));
147 else
148 return first_head ();
153 Stem::heads_i ()const
155 Pointer_group_interface gi (this, "heads");
156 return gi.count ();
160 The note head which forms one end of the stem.
162 Note_head*
163 Stem::first_head () const
165 return extremal_heads ()[-get_direction ()];
169 START is part where stem reaches `last' head.
171 Drul_array<Note_head*>
172 Stem::extremal_heads () const
174 const int inf = 1000000;
175 Drul_array<int> extpos;
176 extpos[DOWN] = inf;
177 extpos[UP] = -inf;
179 Drul_array<Note_head *> exthead;
180 exthead[LEFT] = exthead[RIGHT] =0;
182 for (SCM s = get_elt_pointer ("heads"); gh_pair_p (s); s = gh_cdr (s))
184 Note_head * n = dynamic_cast<Note_head*> (unsmob_element (gh_car (s)));
185 Staff_symbol_referencer_interface si (n);
187 int p = int(si.position_f ());
189 Direction d = LEFT;
190 do {
191 if (d* p > d* extpos[d])
193 exthead[d] = n;
194 extpos[d] = p;
196 } while (flip (&d) != DOWN);
199 return exthead;
202 void
203 Stem::add_head (Rhythmic_head *n)
205 n->set_elt_pointer ("stem", this->self_scm_);
206 n->add_dependency (this);
208 if (Note_head *nh = dynamic_cast<Note_head *> (n))
210 Pointer_group_interface gi (this);
211 gi.name_ = "heads";
213 gi.add_element (n);
215 else
217 n->set_elt_pointer ("rest", n->self_scm_);
221 Stem::Stem (SCM s)
222 : Item (s)
224 set_elt_pointer ("heads", SCM_EOL);
226 add_offset_callback ( &Stem::off_callback, X_AXIS);
229 bool
230 Stem::invisible_b () const
233 UGH. Who determines balltype for stem?
235 Note_head * nh = dynamic_cast<Note_head*> (support_head ());
236 return !(heads_i () && nh->balltype_i () >= 1);
240 Stem::get_center_distance (Direction d) const
242 int staff_center = 0;
243 int distance = (int) (d*(head_positions()[d] - staff_center));
244 return distance >? 0;
247 Direction
248 Stem::get_default_dir () const
250 int du = get_center_distance (UP);
251 int dd = get_center_distance (DOWN);
253 if (sign (dd - du))
254 return Direction (sign (dd -du));
256 return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
260 ugh. A is used for different purposes. This functionality should be
261 moved into scheme at some point to get rid of the silly
262 conversions. (but lets wait till we have namespaces in SCM)
264 Real
265 Stem::get_default_stem_end_position () const
267 bool grace_b = to_boolean (get_elt_property ("grace"));
268 String type_str = grace_b ? "grace-" : "";
269 SCM s;
270 Array<Real> a;
272 Real length_f = 0.;
273 SCM scm_len = get_elt_property("length");
274 if (gh_number_p (scm_len))
276 length_f = gh_scm2double (scm_len);
278 else
280 s = scm_eval (ly_symbol2scm ((type_str + "stem-length").ch_C()));
281 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
282 a.push (gh_scm2double (gh_car (q)));
284 // stem uses half-spaces
285 length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
289 a.clear ();
290 s = scm_eval (ly_symbol2scm ((type_str + "stem-shorten").ch_C()));
291 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
292 a.push (gh_scm2double (gh_car (q)));
295 // stem uses half-spaces
297 // fixme: use gh_list_ref () iso. array[]
298 Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
300 /* URGURGURG
301 'set-default-stemlen' sets direction too
303 Direction dir = get_direction ();
304 if (!dir)
306 dir = get_default_dir ();
307 Directional_element_interface (this).set (dir);
311 stems in unnatural (forced) direction should be shortened,
312 according to [Roush & Gourlay]
314 if (((int)chord_start_f ())
315 && (get_direction () != get_default_dir ()))
316 length_f -= shorten_f;
319 Real st = head_positions()[dir] + dir * length_f;
321 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
322 if (!grace_b && !no_extend_b && dir * st < 0)
323 st = 0.0;
325 return st;
329 FIXME: wrong name
332 Stem::flag_i () const
334 SCM s = get_elt_property ("duration-log");
335 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
338 void
339 Stem::position_noteheads ()
341 if (!heads_i ())
342 return;
344 Link_array<Score_element> heads =
345 Pointer_group_interface__extract_elements (this, (Score_element*)0, "heads");
347 heads.sort (compare_position);
348 Direction dir =get_direction ();
350 if (dir < 0)
351 heads.reverse ();
354 Real w = support_head ()->extent (X_AXIS)[dir];
355 for (int i=0; i < heads.size (); i++)
357 heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
360 bool parity= true; // todo: make this settable.
361 int lastpos = int (Staff_symbol_referencer_interface (heads[0]).position_f ());
362 for (int i=1; i < heads.size (); i ++)
364 Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
365 int dy =abs (lastpos- (int)p);
367 if (dy <= 1)
369 if (parity)
371 Real l = heads[i]->extent (X_AXIS).length ();
372 heads[i]->translate_axis (l * get_direction (), X_AXIS);
374 parity = !parity;
376 else
377 parity = true;
379 lastpos = int (p);
383 GLUE_SCORE_ELEMENT(Stem,before_line_breaking);
385 Stem::member_before_line_breaking ()
387 stem_end_position (); // ugh. Trigger direction calc.
388 position_noteheads ();
390 if (invisible_b ())
392 remove_elt_property ("molecule-callback");
393 // suicide();
396 set_spacing_hints ();
397 return SCM_UNDEFINED;
403 set stem directions for hinting the optical spacing correction.
405 Modifies DIR_LIST property of the Stem's Paper_column
407 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
409 void
410 Stem::set_spacing_hints ()
412 if (!invisible_b ())
414 SCM scmdir = gh_int2scm (get_direction ());
415 SCM dirlist = column_l ()->get_elt_property ("dir-list");
416 if (dirlist == SCM_UNDEFINED)
417 dirlist = SCM_EOL;
419 if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
421 dirlist = gh_cons (scmdir, dirlist);
422 column_l ()->set_elt_property ("dir-list", dirlist);
427 Molecule
428 Stem::flag () const
430 String style;
431 SCM st = get_elt_property ("flag-style");
432 if ( gh_string_p (st))
434 style = ly_scm2string (st);
437 char c = (get_direction () == UP) ? 'u' : 'd';
438 Molecule m = lookup_l ()->afm_find (String ("flags-") + to_str (c) +
439 to_str (flag_i ()));
440 if (!style.empty_b ())
441 m.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
442 return m;
445 Interval
446 Stem::dim_callback (Score_element const *se, Axis )
448 Stem * s = dynamic_cast<Stem*> ((Score_element*)se);
450 Interval r (0, 0);
451 if (unsmob_element (s->get_elt_pointer ("beam")) || abs (s->flag_i ()) <= 2)
452 ; // TODO!
453 else
455 r = s->flag ().extent (X_AXIS);
457 return r;
461 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
464 GLUE_SCORE_ELEMENT(Stem,brew_molecule);
467 Stem::member_brew_molecule () const
469 Molecule mol;
471 Staff_symbol_referencer_interface si (first_head ());
473 Real y1 = si.position_f();
474 Real y2 = stem_end_position ();
476 Interval stem_y(y1,y2);
477 stem_y.unite (Interval (y2,y1));
479 Real dy = staff_symbol_referencer (this).staff_space ()/2.0;
480 Real head_wid = 0;
481 if (support_head ())
482 head_wid = support_head ()->extent (X_AXIS).length ();
483 stem_y[Direction(-get_direction ())] += get_direction () * head_wid * tan(ANGLE)/(2*dy);
485 if (!invisible_b ())
487 Real stem_width = paper_l ()->get_var ("stemthickness");
488 Molecule ss =lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
489 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
490 mol.add_molecule (ss);
493 if (!beam_l () && abs (flag_i ()) > 2)
495 Molecule fl = flag ();
496 fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
497 mol.add_molecule (fl);
500 return mol.create_scheme();
503 Real
504 Stem::off_callback (Score_element const* se, Axis)
506 Stem *st = dynamic_cast<Stem*> ((Score_element*)se);
508 Real r=0;
509 if (Note_head * f = st->first_head ())
511 Interval head_wid(0, f->extent (X_AXIS).length ());
513 if (to_boolean (st->get_elt_property ("stem-centered")))
514 return head_wid.center ();
516 Real rule_thick = st->paper_l ()->get_var ("stemthickness");
517 Direction d = st->get_direction ();
518 r = head_wid[d] - d * rule_thick ;
520 return r;
525 Beam*
526 Stem::beam_l ()const
528 SCM b= get_elt_pointer ("beam");
529 return dynamic_cast<Beam*> (unsmob_element (b));
533 // ugh still very long.
534 Stem_info
535 Stem::calc_stem_info () const
537 assert (beam_l ());
539 Direction beam_dir = Directional_element_interface (beam_l ()).get ();
540 if (!beam_dir)
542 programming_error ("Beam dir not set.");
543 beam_dir = UP;
546 Staff_symbol_referencer_interface st (this);
547 Real staff_space = st.staff_space ();
548 Real half_space = staff_space / 2;
549 Real interbeam_f = paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
550 Real thick = gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
551 int multiplicity = beam_l ()->get_multiplicity ();
553 Stem_info info;
554 info.idealy_f_ = chord_start_f ();
556 // for simplicity, we calculate as if dir == UP
557 info.idealy_f_ *= beam_dir;
558 SCM grace_prop = get_elt_property ("grace");
560 bool grace_b = to_boolean (grace_prop);
562 Array<Real> a;
563 SCM s;
564 String type_str = grace_b ? "grace-" : "";
566 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-minimum-length").ch_C()));
567 a.clear ();
568 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
569 a.push (gh_scm2double (gh_car (q)));
572 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
573 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
575 a.clear();
576 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
577 a.push (gh_scm2double (gh_car (q)));
579 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
581 if (!beam_dir || (beam_dir == Directional_element_interface (this).get ()))
582 /* normal beamed stem */
584 if (multiplicity)
586 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
588 info.miny_f_ = info.idealy_f_;
589 info.maxy_f_ = INT_MAX;
591 info.idealy_f_ += stem_length;
592 info.miny_f_ += minimum_length;
595 lowest beam of (UP) beam must never be lower than second staffline
597 Hmm, reference (Wanske?)
599 Although this (additional) rule is probably correct,
600 I expect that highest beam (UP) should also never be lower
601 than middle staffline, just as normal stems.
604 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
605 if (!grace_b && !no_extend_b)
607 /* highest beam of (UP) beam must never be lower than middle
608 staffline
609 lowest beam of (UP) beam must never be lower than second staffline
611 info.miny_f_ =
612 info.miny_f_ >? 0
613 >? (- 2 * half_space - thick
614 + (multiplicity > 0) * thick
615 + interbeam_f * (multiplicity - 1));
618 else
619 /* knee */
621 info.idealy_f_ -= thick;
622 info.maxy_f_ = info.idealy_f_;
623 info.miny_f_ = -INT_MAX;
625 info.idealy_f_ -= stem_length;
626 info.maxy_f_ -= minimum_length;
629 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
631 s = beam_l ()->get_elt_property ("shorten");
632 if (gh_number_p (s))
633 info.idealy_f_ -= gh_scm2double (s);
635 Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
637 info.idealy_f_ += interstaff_f;
638 info.miny_f_ += interstaff_f;
639 info.maxy_f_ += interstaff_f ;
641 return info;