lilypond-1.3.65
[lilypond.git] / lily / stem.cc
blobab18bcd9b1167eeab7b9e7156628bac05c466893
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 Pointer_group_interface gi (this);
209 if (Note_head *nh = dynamic_cast<Note_head *> (n))
210 gi.name_ = "heads";
211 else
212 gi.name_ = "rests";
214 gi.add_element (n);
217 Stem::Stem (SCM s)
218 : Item (s)
220 set_elt_pointer ("heads", SCM_EOL);
221 set_elt_pointer ("rests", SCM_EOL);
223 add_offset_callback ( &Stem::off_callback, X_AXIS);
226 bool
227 Stem::invisible_b () const
230 UGH. Who determines balltype for stem?
232 Note_head * nh = dynamic_cast<Note_head*> (support_head ());
233 return !(heads_i () && nh->balltype_i () >= 1);
237 Stem::get_center_distance (Direction d) const
239 int staff_center = 0;
240 int distance = (int) (d*(head_positions()[d] - staff_center));
241 return distance >? 0;
244 Direction
245 Stem::get_default_dir () const
247 int du = get_center_distance (UP);
248 int dd = get_center_distance (DOWN);
250 if (sign (dd - du))
251 return Direction (sign (dd -du));
253 return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
257 ugh. A is used for different purposes. This functionality should be
258 moved into scheme at some point to get rid of the silly
259 conversions. (but lets wait till we have namespaces in SCM)
261 Real
262 Stem::get_default_stem_end_position () const
264 bool grace_b = to_boolean (get_elt_property ("grace"));
265 String type_str = grace_b ? "grace-" : "";
266 SCM s;
267 Array<Real> a;
269 Real length_f = 0.;
270 SCM scm_len = get_elt_property("length");
271 if (gh_number_p (scm_len))
273 length_f = gh_scm2double (scm_len);
275 else
277 s = scm_eval (ly_symbol2scm ((type_str + "stem-length").ch_C()));
278 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
279 a.push (gh_scm2double (gh_car (q)));
281 // stem uses half-spaces
282 length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
286 a.clear ();
287 s = scm_eval (ly_symbol2scm ((type_str + "stem-shorten").ch_C()));
288 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
289 a.push (gh_scm2double (gh_car (q)));
292 // stem uses half-spaces
294 // fixme: use gh_list_ref () iso. array[]
295 Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
297 /* URGURGURG
298 'set-default-stemlen' sets direction too
300 Direction dir = get_direction ();
301 if (!dir)
303 dir = get_default_dir ();
304 Directional_element_interface (this).set (dir);
308 stems in unnatural (forced) direction should be shortened,
309 according to [Roush & Gourlay]
311 if (((int)chord_start_f ())
312 && (get_direction () != get_default_dir ()))
313 length_f -= shorten_f;
316 Real st = head_positions()[dir] + dir * length_f;
318 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
319 if (!grace_b && !no_extend_b && dir * st < 0)
320 st = 0.0;
322 return st;
326 FIXME: wrong name
329 Stem::flag_i () const
331 SCM s = get_elt_property ("duration-log");
332 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
335 void
336 Stem::position_noteheads ()
338 if (!heads_i ())
339 return;
341 Link_array<Score_element> heads =
342 Pointer_group_interface__extract_elements (this, (Score_element*)0, "heads");
344 heads.sort (compare_position);
345 Direction dir =get_direction ();
347 if (dir < 0)
348 heads.reverse ();
351 Real w = support_head ()->extent (X_AXIS)[dir];
352 for (int i=0; i < heads.size (); i++)
354 heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
357 bool parity= true; // todo: make this settable.
358 int lastpos = int (Staff_symbol_referencer_interface (heads[0]).position_f ());
359 for (int i=1; i < heads.size (); i ++)
361 Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
362 int dy =abs (lastpos- (int)p);
364 if (dy <= 1)
366 if (parity)
368 Real l = heads[i]->extent (X_AXIS).length ();
369 heads[i]->translate_axis (l * get_direction (), X_AXIS);
371 parity = !parity;
373 else
374 parity = true;
376 lastpos = int (p);
380 GLUE_SCORE_ELEMENT(Stem,before_line_breaking);
382 Stem::member_before_line_breaking ()
384 stem_end_position (); // ugh. Trigger direction calc.
385 position_noteheads ();
387 if (invisible_b ())
389 remove_elt_property ("molecule-callback");
390 // suicide();
393 set_spacing_hints ();
394 return SCM_UNDEFINED;
400 set stem directions for hinting the optical spacing correction.
402 Modifies DIR_LIST property of the Stem's Paper_column
404 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
406 void
407 Stem::set_spacing_hints ()
409 if (!invisible_b ())
411 SCM scmdir = gh_int2scm (get_direction ());
412 SCM dirlist = column_l ()->get_elt_property ("dir-list");
413 if (dirlist == SCM_UNDEFINED)
414 dirlist = SCM_EOL;
416 if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
418 dirlist = gh_cons (scmdir, dirlist);
419 column_l ()->set_elt_property ("dir-list", dirlist);
424 Molecule
425 Stem::flag () const
427 String style;
428 SCM st = get_elt_property ("flag-style");
429 if ( gh_string_p (st))
431 style = ly_scm2string (st);
434 char c = (get_direction () == UP) ? 'u' : 'd';
435 Molecule m = lookup_l ()->afm_find (String ("flags-") + to_str (c) +
436 to_str (flag_i ()));
437 if (!style.empty_b ())
438 m.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
439 return m;
442 Interval
443 Stem::dim_callback (Score_element const *se, Axis )
445 Stem * s = dynamic_cast<Stem*> ((Score_element*)se);
447 Interval r (0, 0);
448 if (unsmob_element (s->get_elt_pointer ("beam")) || abs (s->flag_i ()) <= 2)
449 ; // TODO!
450 else
452 r = s->flag ().extent (X_AXIS);
454 return r;
458 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
461 GLUE_SCORE_ELEMENT(Stem,brew_molecule);
464 Stem::member_brew_molecule () const
466 Molecule mol;
468 Staff_symbol_referencer_interface si (first_head ());
470 Real y1 = si.position_f();
471 Real y2 = stem_end_position ();
473 Interval stem_y(y1,y2);
474 stem_y.unite (Interval (y2,y1));
476 Real dy = staff_symbol_referencer (this).staff_space ()/2.0;
477 Real head_wid = 0;
478 if (support_head ())
479 head_wid = support_head ()->extent (X_AXIS).length ();
480 stem_y[Direction(-get_direction ())] += get_direction () * head_wid * tan(ANGLE)/(2*dy);
482 if (!invisible_b ())
484 Real stem_width = paper_l ()->get_var ("stemthickness");
485 Molecule ss =lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
486 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
487 mol.add_molecule (ss);
490 if (!beam_l () && abs (flag_i ()) > 2)
492 Molecule fl = flag ();
493 fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
494 mol.add_molecule (fl);
497 return mol.create_scheme();
500 Real
501 Stem::off_callback (Score_element const* se, Axis)
503 Stem *st = dynamic_cast<Stem*> ((Score_element*)se);
505 Real r=0;
506 if (Note_head * f = st->first_head ())
508 Interval head_wid(0, f->extent (X_AXIS).length ());
510 if (to_boolean (st->get_elt_property ("stem-centered")))
511 return head_wid.center ();
513 Real rule_thick = st->paper_l ()->get_var ("stemthickness");
514 Direction d = st->get_direction ();
515 r = head_wid[d] - d * rule_thick ;
517 return r;
522 Beam*
523 Stem::beam_l ()const
525 SCM b= get_elt_pointer ("beam");
526 return dynamic_cast<Beam*> (unsmob_element (b));
530 // ugh still very long.
531 Stem_info
532 Stem::calc_stem_info () const
534 assert (beam_l ());
536 Direction beam_dir = Directional_element_interface (beam_l ()).get ();
537 if (!beam_dir)
539 programming_error ("Beam dir not set.");
540 beam_dir = UP;
543 Staff_symbol_referencer_interface st (this);
544 Real staff_space = st.staff_space ();
545 Real half_space = staff_space / 2;
546 Real interbeam_f = paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
547 Real thick = gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
548 int multiplicity = beam_l ()->get_multiplicity ();
550 Stem_info info;
551 info.idealy_f_ = chord_start_f ();
553 // for simplicity, we calculate as if dir == UP
554 info.idealy_f_ *= beam_dir;
555 SCM grace_prop = get_elt_property ("grace");
557 bool grace_b = to_boolean (grace_prop);
559 Array<Real> a;
560 SCM s;
561 String type_str = grace_b ? "grace-" : "";
563 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-minimum-length").ch_C()));
564 a.clear ();
565 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
566 a.push (gh_scm2double (gh_car (q)));
569 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
570 s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
572 a.clear();
573 for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
574 a.push (gh_scm2double (gh_car (q)));
576 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
578 if (!beam_dir || (beam_dir == Directional_element_interface (this).get ()))
579 /* normal beamed stem */
581 if (multiplicity)
583 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
585 info.miny_f_ = info.idealy_f_;
586 info.maxy_f_ = INT_MAX;
588 info.idealy_f_ += stem_length;
589 info.miny_f_ += minimum_length;
592 lowest beam of (UP) beam must never be lower than second staffline
594 Hmm, reference (Wanske?)
596 Although this (additional) rule is probably correct,
597 I expect that highest beam (UP) should also never be lower
598 than middle staffline, just as normal stems.
601 bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
602 if (!grace_b && !no_extend_b)
604 /* highest beam of (UP) beam must never be lower than middle
605 staffline
606 lowest beam of (UP) beam must never be lower than second staffline
608 info.miny_f_ =
609 info.miny_f_ >? 0
610 >? (- 2 * half_space - thick
611 + (multiplicity > 0) * thick
612 + interbeam_f * (multiplicity - 1));
615 else
616 /* knee */
618 info.idealy_f_ -= thick;
619 info.maxy_f_ = info.idealy_f_;
620 info.miny_f_ = -INT_MAX;
622 info.idealy_f_ -= stem_length;
623 info.maxy_f_ -= minimum_length;
626 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
628 s = beam_l ()->get_elt_property ("shorten");
629 if (gh_number_p (s))
630 info.idealy_f_ -= gh_scm2double (s);
632 Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
634 info.idealy_f_ += interstaff_f;
635 info.miny_f_ += interstaff_f;
636 info.maxy_f_ += interstaff_f ;
638 return info;