]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.3.44
[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 (this).get ();
99
100   if (!d)
101     {
102        Stem * me = (Stem*) this;
103        d = get_default_dir ();
104        // urg, AAARGH!
105        directional_element (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_property ("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_property ("heads")));
146     }
147   else
148     return first_head ();
149 }
150
151
152 int
153 Stem::heads_i ()const
154 {
155   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_property ("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_property ("stem", this->self_scm_);
206   n->add_dependency (this);
207
208   Group_interface gi (this);
209   if (Note_head *nh = dynamic_cast<Note_head *> (n))
210     gi.name_ = "heads";
211   else
212     gi.name_ = "rests";
213
214   gi.add_element (n);
215 }
216
217 Stem::Stem ()
218 {
219   set_elt_property ("heads", SCM_EOL);
220   set_elt_property ("rests", SCM_EOL);
221
222   add_offset_callback ( &Stem::off_callback, X_AXIS);
223 }
224
225 bool
226 Stem::invisible_b () const
227 {
228   /*
229     UGH. Who determines balltype for stem?
230    */
231   Note_head * nh = dynamic_cast<Note_head*> (support_head ());
232   return !(heads_i () && nh->balltype_i () >= 1);
233 }
234
235 int
236 Stem::get_center_distance (Direction d) const
237 {
238   int staff_center = 0;
239   int distance = (int) (d*(head_positions()[d] - staff_center));
240   return distance >? 0;
241 }
242
243 Direction
244 Stem::get_default_dir () const
245 {
246   int du = get_center_distance (UP);
247   int dd = get_center_distance (DOWN);
248
249   if (sign (dd - du))
250     return Direction (sign (dd -du));
251
252   return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
253 }
254
255 /*
256   ugh. A is used for different purposes. This functionality should be
257   moved into scheme at some point to get rid of the silly
258   conversions. (but lets wait till we have namespaces in SCM)
259  */
260 Real
261 Stem::get_default_stem_end_position () const
262 {
263   bool grace_b = to_boolean (get_elt_property ("grace"));
264   String type_str = grace_b ? "grace-" : "";
265   SCM s;
266   Array<Real> a;
267
268   Real length_f = 0.;
269   SCM scm_len = get_elt_property("length");
270   if (gh_number_p (scm_len))
271     {
272       length_f = gh_scm2double (scm_len);
273     }
274   else
275     {
276       s = scm_eval (ly_symbol2scm ((type_str + "stem-length").ch_C()));
277       for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
278         a.push (gh_scm2double (gh_car (q)));
279                 
280       // stem uses half-spaces
281       length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
282     }
283
284
285   a.clear ();
286   s = scm_eval (ly_symbol2scm ((type_str + "stem-shorten").ch_C()));
287   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
288     a.push (gh_scm2double (gh_car (q)));
289
290
291   // stem uses half-spaces
292
293   // fixme: use gh_list_ref () iso. array[]
294   Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
295
296   /* URGURGURG
297      'set-default-stemlen' sets direction too
298    */
299   Direction dir = get_direction ();
300   if (!dir)
301     {
302       dir = get_default_dir ();
303       directional_element (this).set (dir);
304     }
305   
306   /* 
307     stems in unnatural (forced) direction should be shortened, 
308     according to [Roush & Gourlay]
309    */
310   if (((int)chord_start_f ())
311       && (get_direction () != get_default_dir ()))
312     length_f -= shorten_f;
313
314
315    Real st = head_positions()[dir] + dir * length_f;
316   
317    bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
318     if (!grace_b && !no_extend_b && dir * st < 0)
319       st = 0.0;
320
321   return st;
322 }
323
324 /*
325   FIXME: wrong name
326  */
327 int
328 Stem::flag_i () const
329 {
330   SCM s = get_elt_property ("duration-log");
331   return  (gh_number_p (s)) ? gh_scm2int (s) : 2;
332 }
333
334 void
335 Stem::position_noteheads ()
336 {
337   if (!heads_i ())
338     return;
339   
340   Link_array<Score_element> heads =
341     Group_interface__extract_elements (this, (Score_element*)0, "heads");
342
343   heads.sort (compare_position);
344   Direction dir =get_direction ();
345   
346   if (dir < 0)
347     heads.reverse ();
348
349
350   Real w = support_head ()->extent (X_AXIS)[dir];
351   for (int i=0; i < heads.size (); i++)
352     {
353       heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
354     }
355   
356   bool parity= true;            // todo: make this settable.
357   int lastpos = int (Staff_symbol_referencer_interface (heads[0]).position_f ());
358   for (int i=1; i < heads.size (); i ++)
359     {
360       Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
361       int dy =abs (lastpos- (int)p);
362
363       if (dy <= 1)
364         {
365           if (parity)
366             {
367               Real l  = heads[i]->extent (X_AXIS).length ();
368               heads[i]->translate_axis (l * get_direction (), X_AXIS);
369             }
370           parity = !parity;
371         }
372       else
373         parity = true;
374       
375       lastpos = int (p);
376     }
377 }
378
379 void
380 Stem::before_line_breaking ()
381 {
382   stem_end_position (); // ugh. Trigger direction calc.
383   position_noteheads ();
384
385   if (invisible_b ())
386     {
387       set_elt_property ("transparent", SCM_BOOL_T);
388       set_extent_callback (0, Y_AXIS);      
389       set_extent_callback (0, X_AXIS);      
390     }
391
392   set_spacing_hints ();
393 }
394
395
396
397 /**
398    set stem directions for hinting the optical spacing correction.
399
400    Modifies DIR_LIST property of the Stem's Paper_column
401
402    TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
403  */
404 void
405 Stem::set_spacing_hints () 
406 {
407   if (!invisible_b ())
408     {
409       SCM scmdir  = gh_int2scm (get_direction ());
410       SCM dirlist = column_l ()->get_elt_property ("dir-list");
411       if (dirlist == SCM_UNDEFINED)
412         dirlist = SCM_EOL;
413
414       if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
415         {
416           dirlist = gh_cons (scmdir, dirlist);
417           column_l ()->set_elt_property ("dir-list", dirlist);
418         }
419     }
420 }
421
422 Molecule
423 Stem::flag () const
424 {
425   String style;
426   SCM st = get_elt_property ("flag-style");
427   if ( gh_string_p (st))
428     {
429       style = ly_scm2string (st);
430     }
431
432   char c = (get_direction () == UP) ? 'u' : 'd';
433   Molecule m = lookup_l ()->afm_find (String ("flags-") + to_str (c) + 
434                                       to_str (flag_i ()));
435   if (!style.empty_b ())
436     m.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
437   return m;
438 }
439
440 Interval
441 Stem::dim_callback (Dimension_cache const* c) 
442 {
443   Stem * s = dynamic_cast<Stem*> (c->element_l ());
444   
445   Interval r (0, 0);
446   if (unsmob_element (s->get_elt_property ("beam")) || abs (s->flag_i ()) <= 2)
447     ;   // TODO!
448   else
449     {
450       r = s->flag ().dim_.x ();
451     }
452   return r;
453 }
454
455
456 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh!
457
458 Molecule 
459 Stem::do_brew_molecule () const
460 {
461   Molecule mol;
462
463   Staff_symbol_referencer_interface si (first_head ());
464   
465   Real y1 = si.position_f();
466   Real y2 = stem_end_position ();
467   
468   Interval stem_y(y1,y2);
469   stem_y.unite (Interval (y2,y1));
470
471   Real dy = staff_symbol_referencer (this).staff_space ()/2.0;
472   Real head_wid = 0;
473   if (support_head ())
474     head_wid = support_head ()->extent (X_AXIS).length ();
475   stem_y[Direction(-get_direction ())] += get_direction () * head_wid * tan(ANGLE)/(2*dy);
476   
477   if (!invisible_b ())
478     {
479       Real stem_width = paper_l ()->get_var ("stemthickness");
480       Molecule ss =lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
481                                                  Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
482       mol.add_molecule (ss);
483     }
484
485   if (!beam_l () && abs (flag_i ()) > 2)
486     {
487       Molecule fl = flag ();
488       fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
489       mol.add_molecule (fl);
490     }
491
492   return mol;
493 }
494
495 Real
496 Stem::off_callback (Dimension_cache const * c)
497 {
498   Stem * st = dynamic_cast<Stem*> (c->element_l ());
499
500   Real r=0;
501   if (Note_head * f = st->first_head ())
502     {
503       Interval head_wid(0, f->extent (X_AXIS).length ());
504
505       if (to_boolean (st->get_elt_property ("stem-centered")))
506         return head_wid.center ();
507       
508       Real rule_thick = st->paper_l ()->get_var ("stemthickness");
509       Direction d = st->get_direction ();
510       r = head_wid[d] - d * rule_thick ;
511     }
512   return r;
513 }
514
515
516
517 Beam*
518 Stem::beam_l ()const
519 {
520   SCM b=  get_elt_property ("beam");
521   return dynamic_cast<Beam*> (unsmob_element (b));
522 }
523
524
525 // ugh still very long.
526 Stem_info
527 Stem::calc_stem_info () const
528 {
529   assert (beam_l ());
530
531   Direction beam_dir = directional_element (beam_l ()).get ();
532   if (!beam_dir)
533     {
534       programming_error ("Beam dir not set.");
535       beam_dir = UP;
536     }
537     
538   Staff_symbol_referencer_interface st (this);
539   Real staff_space = st.staff_space ();
540   Real half_space = staff_space / 2;
541   Real interbeam_f = paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
542   Real thick = gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
543   int multiplicity = beam_l ()->get_multiplicity ();
544
545   Stem_info info; 
546   info.idealy_f_ = chord_start_f ();
547
548   // for simplicity, we calculate as if dir == UP
549   info.idealy_f_ *= beam_dir;
550   SCM grace_prop = get_elt_property ("grace");
551
552   bool grace_b = to_boolean (grace_prop);
553   
554   Array<Real> a;
555   SCM s;
556   String type_str = grace_b ? "grace-" : "";
557   
558   s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-minimum-length").ch_C()));
559   a.clear ();
560   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
561     a.push (gh_scm2double (gh_car (q)));
562
563
564   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
565   s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
566
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   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
572
573   if (!beam_dir || (beam_dir == directional_element (this).get ()))
574     /* normal beamed stem */
575     {
576       if (multiplicity)
577         {
578           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
579         }
580       info.miny_f_ = info.idealy_f_;
581       info.maxy_f_ = INT_MAX;
582
583       info.idealy_f_ += stem_length;
584       info.miny_f_ += minimum_length;
585
586       /*
587         lowest beam of (UP) beam must never be lower than second staffline
588
589         Hmm, reference (Wanske?)
590
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.
594         
595       */
596       bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
597       if (!grace_b && !no_extend_b)
598         {
599           /* highest beam of (UP) beam must never be lower than middle
600              staffline
601              lowest beam of (UP) beam must never be lower than second staffline
602            */
603           info.miny_f_ =
604             info.miny_f_ >? 0
605             >? (- 2 * half_space - thick
606                 + (multiplicity > 0) * thick
607                 + interbeam_f * (multiplicity - 1));
608         }
609     }
610   else
611     /* knee */
612     {
613       info.idealy_f_ -= thick;
614       info.maxy_f_ = info.idealy_f_;
615       info.miny_f_ = -INT_MAX;
616
617       info.idealy_f_ -= stem_length;
618       info.maxy_f_ -= minimum_length;
619     }
620   
621   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
622
623   s = beam_l ()->get_elt_property ("shorten");
624   if (gh_number_p (s))
625     info.idealy_f_ -= gh_double2scm (s);
626
627   Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
628
629   info.idealy_f_ += interstaff_f;
630   info.miny_f_ += interstaff_f;
631   info.maxy_f_ += interstaff_f ;
632
633   return info;
634 }
635