]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.3.33
[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, 1997--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 #include "lily-guile.icc"
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   dim_cache_[X_AXIS]->off_callbacks_.push ( &Stem::off_callback);
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 Real
256 Stem::get_default_stem_end_position () const
257 {
258   bool grace_b = to_boolean (get_elt_property ("grace"));
259   String type_str = grace_b ? "grace-" : "";
260   SCM s;
261   Array<Real> a;
262
263   Real length_f = 0.;
264   SCM scm_len = get_elt_property("length");
265   if (gh_number_p (scm_len))
266     {
267       length_f = gh_scm2double (scm_len);
268     }
269   else
270     {
271       s = ly_eval_str (type_str + "stem-length");
272       scm_to_array (s, &a);
273       // stem uses half-spaces
274       length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
275     }
276
277
278   s = ly_eval_str (type_str + "stem-shorten");
279   scm_to_array (s, &a);
280
281   // stem uses half-spaces
282
283   // fixme: use gh_list_ref () iso. array[]
284   Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
285
286   /* URGURGURG
287      'set-default-stemlen' sets direction too
288    */
289   Direction dir = get_direction ();
290   if (!dir)
291     {
292       dir = get_default_dir ();
293       directional_element (this).set (dir);
294     }
295   
296   /* 
297     stems in unnatural (forced) direction should be shortened, 
298     according to [Roush & Gourlay]
299    */
300   if (((int)chord_start_f ())
301       && (get_direction () != get_default_dir ()))
302     length_f -= shorten_f;
303
304
305    Real st = head_positions()[dir] + dir * length_f;
306   
307    bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
308     if (!grace_b && !no_extend_b && dir * st < 0)
309       st = 0.0;
310
311   return st;
312 }
313
314 /*
315   FIXME: wrong name
316  */
317 int
318 Stem::flag_i () const
319 {
320   SCM s = get_elt_property ("duration-log");
321   return  (gh_number_p (s)) ? gh_scm2int (s) : 2;
322 }
323
324 void
325 Stem::position_noteheads ()
326 {
327   if (!heads_i ())
328     return;
329   
330   Link_array<Score_element> heads =
331     Group_interface__extract_elements (this, (Score_element*)0, "heads");
332
333   heads.sort (compare_position);
334   Direction dir =get_direction ();
335   
336   if (dir < 0)
337     heads.reverse ();
338
339
340   Real w = support_head ()->extent (X_AXIS)[dir];
341   for (int i=0; i < heads.size (); i++)
342     {
343       heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
344     }
345   
346   bool parity= true;            // todo: make this settable.
347   int lastpos = int (Staff_symbol_referencer_interface (heads[0]).position_f ());
348   for (int i=1; i < heads.size (); i ++)
349     {
350       Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
351       int dy =abs (lastpos- (int)p);
352
353       if (dy <= 1)
354         {
355           if (parity)
356             {
357               Real l  = heads[i]->extent (X_AXIS).length ();
358               heads[i]->translate_axis (l * get_direction (), X_AXIS);
359             }
360           parity = !parity;
361         }
362       else
363         parity = true;
364       
365       lastpos = int (p);
366     }
367 }
368
369 void
370 Stem::before_line_breaking ()
371 {
372   stem_end_position (); // ugh. Trigger direction calc.
373   position_noteheads ();
374
375   if (invisible_b ())
376     {
377       set_elt_property ("transparent", SCM_BOOL_T);
378       set_empty (Y_AXIS);      
379       set_empty (X_AXIS);      
380     }
381
382   set_spacing_hints ();
383 }
384
385
386
387 /**
388    set stem directions for hinting the optical spacing correction.
389
390    Modifies DIR_LIST property of the Stem's Paper_column
391
392    TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
393  */
394 void
395 Stem::set_spacing_hints () 
396 {
397   if (!invisible_b ())
398     {
399       SCM scmdir  = gh_int2scm (get_direction ());
400       SCM dirlist = column_l ()->get_elt_property ("dir-list");
401       if (dirlist == SCM_UNDEFINED)
402         dirlist = SCM_EOL;
403
404       if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
405         {
406           dirlist = gh_cons (scmdir, dirlist);
407           column_l ()->set_elt_property ("dir-list", dirlist);
408         }
409     }
410 }
411
412 Molecule
413 Stem::flag () const
414 {
415   String style;
416   SCM st = get_elt_property ("flag-style");
417   if ( gh_string_p (st))
418     {
419       style = ly_scm2string (st);
420     }
421
422   char c = (get_direction () == UP) ? 'u' : 'd';
423   Molecule m = lookup_l ()->afm_find (String ("flags-") + to_str (c) + 
424                                       to_str (flag_i ()));
425   if (!style.empty_b ())
426     m.add_molecule(lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
427   return m;
428 }
429
430 Interval
431 Stem::dim_callback (Dimension_cache const* c) 
432 {
433   Stem * s = dynamic_cast<Stem*> (c->element_l ());
434   
435   Interval r (0, 0);
436   if (unsmob_element (s->get_elt_property ("beam")) || abs (s->flag_i ()) <= 2)
437     ;   // TODO!
438   else
439     {
440       r = s->flag ().dim_.x ();
441     }
442   return r;
443 }
444
445
446 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh!
447
448 Molecule 
449 Stem::do_brew_molecule () const
450 {
451   Molecule mol;
452
453   Staff_symbol_referencer_interface si (first_head ());
454   
455   Real y1 = si.position_f();
456   Real y2 = stem_end_position ();
457   
458   Interval stem_y(y1,y2);
459   stem_y.unite (Interval (y2,y1));
460
461   Real dy = staff_symbol_referencer (this).staff_space ()/2.0;
462   Real head_wid = 0;
463   if (support_head ())
464     head_wid = support_head ()->extent (X_AXIS).length ();
465   stem_y[Direction(-get_direction ())] += get_direction () * head_wid * tan(ANGLE)/(2*dy);
466   
467   if (!invisible_b ())
468     {
469       Real stem_width = paper_l ()->get_var ("stemthickness");
470       Molecule ss =lookup_l ()->filledbox (Box (Interval (-stem_width/2, stem_width/2),
471                                                  Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
472       mol.add_molecule (ss);
473     }
474
475   if (!beam_l () && abs (flag_i ()) > 2)
476     {
477       Molecule fl = flag ();
478       fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
479       mol.add_molecule (fl);
480     }
481
482   return mol;
483 }
484
485 Real
486 Stem::off_callback (Dimension_cache const * c)
487 {
488   Stem * st = dynamic_cast<Stem*> (c->element_l ());
489
490   Real r=0;
491   if (Note_head * f = st->first_head ())
492     {
493       Interval head_wid(0, f->extent (X_AXIS).length ());
494
495       if (to_boolean (st->get_elt_property ("stem-centered")))
496         return head_wid.center ();
497       
498       Real rule_thick = st->paper_l ()->get_var ("stemthickness");
499       Direction d = st->get_direction ();
500       r = head_wid[d] - d * rule_thick ;
501     }
502   return r;
503 }
504
505
506
507 Beam*
508 Stem::beam_l ()const
509 {
510   SCM b=  get_elt_property ("beam");
511   return dynamic_cast<Beam*> (unsmob_element (b));
512 }
513
514
515 // ugh still very long.
516 Stem_info
517 Stem::calc_stem_info () const
518 {
519   assert (beam_l ());
520
521   Direction beam_dir = directional_element (beam_l ()).get ();
522   if (!beam_dir)
523     {
524       programming_error ("Beam dir not set.");
525       beam_dir = UP;
526     }
527     
528   Staff_symbol_referencer_interface st (this);
529   Real staff_space = st.staff_space ();
530   Real half_space = staff_space / 2;
531   Real interbeam_f = paper_l ()->interbeam_f (beam_l ()->get_multiplicity ());
532   Real thick = gh_scm2double (beam_l ()->get_elt_property ("beam-thickness"));
533   int multiplicity = beam_l ()->get_multiplicity ();
534
535   Stem_info info; 
536   info.idealy_f_ = chord_start_f ();
537
538   // for simplicity, we calculate as if dir == UP
539   info.idealy_f_ *= beam_dir;
540   SCM grace_prop = get_elt_property ("grace");
541
542   bool grace_b = to_boolean (grace_prop);
543   
544   Array<Real> a;
545   SCM s;
546   String type_str = grace_b ? "grace-" : "";
547   
548   s = ly_eval_str (type_str + "beamed-stem-minimum-length");
549   scm_to_array (s, &a);
550   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
551
552   s = ly_eval_str (type_str + "beamed-stem-length");
553   scm_to_array (s, &a);
554   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
555
556   if (!beam_dir || (beam_dir == directional_element (this).get ()))
557     /* normal beamed stem */
558     {
559       if (multiplicity)
560         {
561           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
562         }
563       info.miny_f_ = info.idealy_f_;
564       info.maxy_f_ = INT_MAX;
565
566       info.idealy_f_ += stem_length;
567       info.miny_f_ += minimum_length;
568
569       /*
570         lowest beam of (UP) beam must never be lower than second staffline
571
572         Hmm, reference (Wanske?)
573
574         Although this (additional) rule is probably correct,
575         I expect that highest beam (UP) should also never be lower
576         than middle staffline, just as normal stems.
577         
578       */
579       bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
580       if (!grace_b && !no_extend_b)
581         {
582           /* highest beam of (UP) beam must never be lower than middle
583              staffline
584              lowest beam of (UP) beam must never be lower than second staffline
585            */
586           info.miny_f_ =
587             info.miny_f_ >? 0
588             >? (- 2 * half_space - thick
589                 + (multiplicity > 0) * thick
590                 + interbeam_f * (multiplicity - 1));
591         }
592     }
593   else
594     /* knee */
595     {
596       info.idealy_f_ -= thick;
597       info.maxy_f_ = info.idealy_f_;
598       info.miny_f_ = -INT_MAX;
599
600       info.idealy_f_ -= stem_length;
601       info.maxy_f_ -= minimum_length;
602     }
603   
604   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
605
606   s = beam_l ()->get_elt_property ("shorten");
607   if (gh_number_p (s))
608     info.idealy_f_ -= gh_double2scm (s);
609
610   Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
611
612   info.idealy_f_ += interstaff_f;
613   info.miny_f_ += interstaff_f;
614   info.maxy_f_ += interstaff_f ;
615
616   return info;
617 }
618