]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.3.66
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7     Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10 */
11 #include <math.h>               // m_pi
12
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"
28
29
30
31 void
32 Stem::set_beaming (int i,  Direction d )
33 {
34   SCM pair = get_elt_property ("beaming");
35   
36   if (!gh_pair_p (pair))
37     {
38       pair = gh_cons (gh_int2scm (0),gh_int2scm (0));
39       set_elt_property ("beaming", pair);
40     }
41   index_set_cell (pair, d, gh_int2scm (i));
42 }
43
44 int
45 Stem::beam_count (Direction d) const
46 {
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;
52 }
53
54 Interval
55 Stem::head_positions () const
56 {
57   if (!heads_i ())
58     {
59       Interval iv;
60       return iv;
61     }
62
63   
64   Drul_array<Note_head*> e (extremal_heads ());
65
66   return Interval (staff_symbol_referencer (e[DOWN]).position_f (),
67                    staff_symbol_referencer( e[UP]).position_f ()); 
68 }
69
70
71 Real
72 Stem::chord_start_f () const
73 {
74   return head_positions()[get_direction ()]
75     * Staff_symbol_referencer_interface (this).staff_space ()/2.0;
76 }
77
78 Real
79 Stem::stem_end_position () const
80 {
81   SCM p =get_elt_property ("stem-end-position");
82   Real pos;
83   if (!gh_number_p (p))
84     {
85       Stem * me = (Stem*) this;
86       pos = get_default_stem_end_position ();
87       me->set_elt_property ("stem-end-position", gh_double2scm (pos));
88     }
89   else
90     pos = gh_scm2double (p);
91
92   return pos;
93 }
94
95 Direction
96 Stem::get_direction () const
97 {
98   Direction d = Directional_element_interface (this).get ();
99
100   if (!d)
101     {
102        Stem * me = (Stem*) this;
103        d = get_default_dir ();
104        // urg, AAARGH!
105        Directional_element_interface (me).set (d);
106     }
107   return d ;
108 }
109
110
111 void
112 Stem::set_stemend (Real se)
113 {
114   // todo: margins
115   Direction d= get_direction ();
116   
117   if (d && d * head_positions()[get_direction ()] >= se*d)
118     warning (_ ("Weird stem size; check for narrow beams"));
119
120   set_elt_property ("stem-end-position", gh_double2scm (se));
121 }
122
123 int
124 Stem::type_i () const
125 {
126   return first_head () ?  first_head ()->balltype_i () : 2;
127 }
128
129 /*
130   Note head that determines hshift for upstems
131  */ 
132 Score_element*
133 Stem::support_head ()const
134 {
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)
140     {
141       /*
142         UGH.
143        */
144       
145       return unsmob_element (gh_car (get_elt_pointer ("heads")));
146     }
147   else
148     return first_head ();
149 }
150
151
152 int
153 Stem::heads_i ()const
154 {
155   Pointer_group_interface gi (this, "heads");
156   return gi.count ();
157 }
158
159 /*
160   The note head which forms one end of the stem.  
161  */
162 Note_head*
163 Stem::first_head () const
164 {
165   return extremal_heads ()[-get_direction ()];
166 }
167
168 /*
169   START is part where stem reaches `last' head. 
170  */
171 Drul_array<Note_head*>
172 Stem::extremal_heads () const
173 {
174   const int inf = 1000000;
175   Drul_array<int> extpos;
176   extpos[DOWN] = inf;
177   extpos[UP] = -inf;  
178   
179   Drul_array<Note_head *> exthead;
180   exthead[LEFT] = exthead[RIGHT] =0;
181   
182   for (SCM s = get_elt_pointer ("heads"); gh_pair_p (s); s = gh_cdr (s))
183     {
184       Note_head * n = dynamic_cast<Note_head*> (unsmob_element (gh_car (s)));
185       Staff_symbol_referencer_interface si (n);
186       
187       int p = int(si.position_f ());
188
189       Direction d = LEFT;
190       do {
191       if (d* p > d* extpos[d])
192         {
193           exthead[d] = n;
194           extpos[d] = p;
195         }
196       } while (flip (&d) != DOWN);
197     }
198
199   return exthead;
200 }
201
202 void
203 Stem::add_head (Rhythmic_head *n)
204 {
205   n->set_elt_pointer ("stem", this->self_scm_);
206   n->add_dependency (this);
207
208   if (Note_head *nh = dynamic_cast<Note_head *> (n))
209     {
210       Pointer_group_interface gi (this);
211       gi.name_ = "heads";
212
213       gi.add_element (n);
214     }
215   else
216     {
217       n->set_elt_pointer ("rest", n->self_scm_);
218     }
219 }
220
221 Stem::Stem (SCM s)
222   : Item (s)
223 {
224   set_elt_pointer ("heads", SCM_EOL);
225
226   add_offset_callback ( &Stem::off_callback, X_AXIS);
227 }
228
229 bool
230 Stem::invisible_b () const
231 {
232   /*
233     UGH. Who determines balltype for stem?
234    */
235   Note_head * nh = dynamic_cast<Note_head*> (support_head ());
236   return !(heads_i () && nh->balltype_i () >= 1);
237 }
238
239 int
240 Stem::get_center_distance (Direction d) const
241 {
242   int staff_center = 0;
243   int distance = (int) (d*(head_positions()[d] - staff_center));
244   return distance >? 0;
245 }
246
247 Direction
248 Stem::get_default_dir () const
249 {
250   int du = get_center_distance (UP);
251   int dd = get_center_distance (DOWN);
252
253   if (sign (dd - du))
254     return Direction (sign (dd -du));
255
256   return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
257 }
258
259 /*
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)
263  */
264 Real
265 Stem::get_default_stem_end_position () const
266 {
267   bool grace_b = to_boolean (get_elt_property ("grace"));
268   String type_str = grace_b ? "grace-" : "";
269   SCM s;
270   Array<Real> a;
271
272   Real length_f = 0.;
273   SCM scm_len = get_elt_property("length");
274   if (gh_number_p (scm_len))
275     {
276       length_f = gh_scm2double (scm_len);
277     }
278   else
279     {
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)));
283                 
284       // stem uses half-spaces
285       length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
286     }
287
288
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)));
293
294
295   // stem uses half-spaces
296
297   // fixme: use gh_list_ref () iso. array[]
298   Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
299
300   /* URGURGURG
301      'set-default-stemlen' sets direction too
302    */
303   Direction dir = get_direction ();
304   if (!dir)
305     {
306       dir = get_default_dir ();
307       Directional_element_interface (this).set (dir);
308     }
309   
310   /* 
311     stems in unnatural (forced) direction should be shortened, 
312     according to [Roush & Gourlay]
313    */
314   if (((int)chord_start_f ())
315       && (get_direction () != get_default_dir ()))
316     length_f -= shorten_f;
317
318
319    Real st = head_positions()[dir] + dir * length_f;
320   
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;
324
325   return st;
326 }
327
328 /*
329   FIXME: wrong name
330  */
331 int
332 Stem::flag_i () const
333 {
334   SCM s = get_elt_property ("duration-log");
335   return  (gh_number_p (s)) ? gh_scm2int (s) : 2;
336 }
337
338 void
339 Stem::position_noteheads ()
340 {
341   if (!heads_i ())
342     return;
343   
344   Link_array<Score_element> heads =
345     Pointer_group_interface__extract_elements (this, (Score_element*)0, "heads");
346
347   heads.sort (compare_position);
348   Direction dir =get_direction ();
349   
350   if (dir < 0)
351     heads.reverse ();
352
353
354   Real w = support_head ()->extent (X_AXIS)[dir];
355   for (int i=0; i < heads.size (); i++)
356     {
357       heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
358     }
359   
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 ++)
363     {
364       Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
365       int dy =abs (lastpos- (int)p);
366
367       if (dy <= 1)
368         {
369           if (parity)
370             {
371               Real l  = heads[i]->extent (X_AXIS).length ();
372               heads[i]->translate_axis (l * get_direction (), X_AXIS);
373             }
374           parity = !parity;
375         }
376       else
377         parity = true;
378       
379       lastpos = int (p);
380     }
381 }
382
383 GLUE_SCORE_ELEMENT(Stem,before_line_breaking);
384 SCM
385 Stem::member_before_line_breaking ()
386 {
387   stem_end_position (); // ugh. Trigger direction calc.
388   position_noteheads ();
389
390   if (invisible_b ())
391     {
392       remove_elt_property ("molecule-callback");
393       // suicide();
394     }
395   
396   set_spacing_hints ();
397   return SCM_UNDEFINED;
398 }
399
400
401
402 /**
403    set stem directions for hinting the optical spacing correction.
404
405    Modifies DIR_LIST property of the Stem's Paper_column
406
407    TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
408  */
409 void
410 Stem::set_spacing_hints () 
411 {
412   if (!invisible_b ())
413     {
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;
418
419       if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
420         {
421           dirlist = gh_cons (scmdir, dirlist);
422           column_l ()->set_elt_property ("dir-list", dirlist);
423         }
424     }
425 }
426
427 Molecule
428 Stem::flag () const
429 {
430   String style;
431   SCM st = get_elt_property ("flag-style");
432   if ( gh_string_p (st))
433     {
434       style = ly_scm2string (st);
435     }
436
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;
443 }
444
445 Interval
446 Stem::dim_callback (Score_element const *se, Axis ) 
447 {
448   Stem * s = dynamic_cast<Stem*> ((Score_element*)se);
449   
450   Interval r (0, 0);
451   if (unsmob_element (s->get_elt_pointer ("beam")) || abs (s->flag_i ()) <= 2)
452     ;   // TODO!
453   else
454     {
455       r = s->flag ().extent (X_AXIS);
456     }
457   return r;
458 }
459
460
461 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
462
463
464 GLUE_SCORE_ELEMENT(Stem,brew_molecule);
465
466 SCM
467 Stem::member_brew_molecule () const
468 {
469   Molecule mol;
470
471   Staff_symbol_referencer_interface si (first_head ());
472   
473   Real y1 = si.position_f();
474   Real y2 = stem_end_position ();
475   
476   Interval stem_y(y1,y2);
477   stem_y.unite (Interval (y2,y1));
478
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);
484   
485   if (!invisible_b ())
486     {
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);
491     }
492
493   if (!beam_l () && abs (flag_i ()) > 2)
494     {
495       Molecule fl = flag ();
496       fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
497       mol.add_molecule (fl);
498     }
499
500   return mol.create_scheme();
501 }
502
503 Real
504 Stem::off_callback (Score_element const* se, Axis)
505 {
506   Stem *st = dynamic_cast<Stem*> ((Score_element*)se);
507
508   Real r=0;
509   if (Note_head * f = st->first_head ())
510     {
511       Interval head_wid(0, f->extent (X_AXIS).length ());
512
513       if (to_boolean (st->get_elt_property ("stem-centered")))
514         return head_wid.center ();
515       
516       Real rule_thick = st->paper_l ()->get_var ("stemthickness");
517       Direction d = st->get_direction ();
518       r = head_wid[d] - d * rule_thick ;
519     }
520   return r;
521 }
522
523
524
525 Beam*
526 Stem::beam_l ()const
527 {
528   SCM b=  get_elt_pointer ("beam");
529   return dynamic_cast<Beam*> (unsmob_element (b));
530 }
531
532
533 // ugh still very long.
534 Stem_info
535 Stem::calc_stem_info () const
536 {
537   assert (beam_l ());
538
539   Direction beam_dir = Directional_element_interface (beam_l ()).get ();
540   if (!beam_dir)
541     {
542       programming_error ("Beam dir not set.");
543       beam_dir = UP;
544     }
545     
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 ();
552
553   Stem_info info; 
554   info.idealy_f_ = chord_start_f ();
555
556   // for simplicity, we calculate as if dir == UP
557   info.idealy_f_ *= beam_dir;
558   SCM grace_prop = get_elt_property ("grace");
559
560   bool grace_b = to_boolean (grace_prop);
561   
562   Array<Real> a;
563   SCM s;
564   String type_str = grace_b ? "grace-" : "";
565   
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)));
570
571
572   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
573   s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
574
575   a.clear();
576   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
577     a.push (gh_scm2double (gh_car (q)));
578
579   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
580
581   if (!beam_dir || (beam_dir == Directional_element_interface (this).get ()))
582     /* normal beamed stem */
583     {
584       if (multiplicity)
585         {
586           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
587         }
588       info.miny_f_ = info.idealy_f_;
589       info.maxy_f_ = INT_MAX;
590
591       info.idealy_f_ += stem_length;
592       info.miny_f_ += minimum_length;
593
594       /*
595         lowest beam of (UP) beam must never be lower than second staffline
596
597         Hmm, reference (Wanske?)
598
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.
602         
603       */
604       bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
605       if (!grace_b && !no_extend_b)
606         {
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
610            */
611           info.miny_f_ =
612             info.miny_f_ >? 0
613             >? (- 2 * half_space - thick
614                 + (multiplicity > 0) * thick
615                 + interbeam_f * (multiplicity - 1));
616         }
617     }
618   else
619     /* knee */
620     {
621       info.idealy_f_ -= thick;
622       info.maxy_f_ = info.idealy_f_;
623       info.miny_f_ = -INT_MAX;
624
625       info.idealy_f_ -= stem_length;
626       info.maxy_f_ -= minimum_length;
627     }
628   
629   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
630
631   s = beam_l ()->get_elt_property ("shorten");
632   if (gh_number_p (s))
633     info.idealy_f_ -= gh_scm2double (s);
634
635   Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
636
637   info.idealy_f_ += interstaff_f;
638   info.miny_f_ += interstaff_f;
639   info.maxy_f_ += interstaff_f ;
640
641   return info;
642 }
643