]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.3.65
[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   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";
213
214   gi.add_element (n);
215 }
216
217 Stem::Stem (SCM s)
218   : Item (s)
219 {
220   set_elt_pointer ("heads", SCM_EOL);
221   set_elt_pointer ("rests", SCM_EOL);
222
223   add_offset_callback ( &Stem::off_callback, X_AXIS);
224 }
225
226 bool
227 Stem::invisible_b () const
228 {
229   /*
230     UGH. Who determines balltype for stem?
231    */
232   Note_head * nh = dynamic_cast<Note_head*> (support_head ());
233   return !(heads_i () && nh->balltype_i () >= 1);
234 }
235
236 int
237 Stem::get_center_distance (Direction d) const
238 {
239   int staff_center = 0;
240   int distance = (int) (d*(head_positions()[d] - staff_center));
241   return distance >? 0;
242 }
243
244 Direction
245 Stem::get_default_dir () const
246 {
247   int du = get_center_distance (UP);
248   int dd = get_center_distance (DOWN);
249
250   if (sign (dd - du))
251     return Direction (sign (dd -du));
252
253   return Direction (int(paper_l ()->get_var ("stem_default_neutral_direction")));
254 }
255
256 /*
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)
260  */
261 Real
262 Stem::get_default_stem_end_position () const
263 {
264   bool grace_b = to_boolean (get_elt_property ("grace"));
265   String type_str = grace_b ? "grace-" : "";
266   SCM s;
267   Array<Real> a;
268
269   Real length_f = 0.;
270   SCM scm_len = get_elt_property("length");
271   if (gh_number_p (scm_len))
272     {
273       length_f = gh_scm2double (scm_len);
274     }
275   else
276     {
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)));
280                 
281       // stem uses half-spaces
282       length_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
283     }
284
285
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)));
290
291
292   // stem uses half-spaces
293
294   // fixme: use gh_list_ref () iso. array[]
295   Real shorten_f = a[((flag_i () - 2) >? 0) <? (a.size () - 1)] * 2;
296
297   /* URGURGURG
298      'set-default-stemlen' sets direction too
299    */
300   Direction dir = get_direction ();
301   if (!dir)
302     {
303       dir = get_default_dir ();
304       Directional_element_interface (this).set (dir);
305     }
306   
307   /* 
308     stems in unnatural (forced) direction should be shortened, 
309     according to [Roush & Gourlay]
310    */
311   if (((int)chord_start_f ())
312       && (get_direction () != get_default_dir ()))
313     length_f -= shorten_f;
314
315
316    Real st = head_positions()[dir] + dir * length_f;
317   
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;
321
322   return st;
323 }
324
325 /*
326   FIXME: wrong name
327  */
328 int
329 Stem::flag_i () const
330 {
331   SCM s = get_elt_property ("duration-log");
332   return  (gh_number_p (s)) ? gh_scm2int (s) : 2;
333 }
334
335 void
336 Stem::position_noteheads ()
337 {
338   if (!heads_i ())
339     return;
340   
341   Link_array<Score_element> heads =
342     Pointer_group_interface__extract_elements (this, (Score_element*)0, "heads");
343
344   heads.sort (compare_position);
345   Direction dir =get_direction ();
346   
347   if (dir < 0)
348     heads.reverse ();
349
350
351   Real w = support_head ()->extent (X_AXIS)[dir];
352   for (int i=0; i < heads.size (); i++)
353     {
354       heads[i]->translate_axis (w - heads[i]->extent (X_AXIS)[dir], X_AXIS);
355     }
356   
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 ++)
360     {
361       Real p = Staff_symbol_referencer_interface (heads[i]).position_f ();
362       int dy =abs (lastpos- (int)p);
363
364       if (dy <= 1)
365         {
366           if (parity)
367             {
368               Real l  = heads[i]->extent (X_AXIS).length ();
369               heads[i]->translate_axis (l * get_direction (), X_AXIS);
370             }
371           parity = !parity;
372         }
373       else
374         parity = true;
375       
376       lastpos = int (p);
377     }
378 }
379
380 GLUE_SCORE_ELEMENT(Stem,before_line_breaking);
381 SCM
382 Stem::member_before_line_breaking ()
383 {
384   stem_end_position (); // ugh. Trigger direction calc.
385   position_noteheads ();
386
387   if (invisible_b ())
388     {
389       remove_elt_property ("molecule-callback");
390       // suicide();
391     }
392   
393   set_spacing_hints ();
394   return SCM_UNDEFINED;
395 }
396
397
398
399 /**
400    set stem directions for hinting the optical spacing correction.
401
402    Modifies DIR_LIST property of the Stem's Paper_column
403
404    TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
405  */
406 void
407 Stem::set_spacing_hints () 
408 {
409   if (!invisible_b ())
410     {
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;
415
416       if (scm_sloppy_memq (scmdir, dirlist) == SCM_EOL)
417         {
418           dirlist = gh_cons (scmdir, dirlist);
419           column_l ()->set_elt_property ("dir-list", dirlist);
420         }
421     }
422 }
423
424 Molecule
425 Stem::flag () const
426 {
427   String style;
428   SCM st = get_elt_property ("flag-style");
429   if ( gh_string_p (st))
430     {
431       style = ly_scm2string (st);
432     }
433
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;
440 }
441
442 Interval
443 Stem::dim_callback (Score_element const *se, Axis ) 
444 {
445   Stem * s = dynamic_cast<Stem*> ((Score_element*)se);
446   
447   Interval r (0, 0);
448   if (unsmob_element (s->get_elt_pointer ("beam")) || abs (s->flag_i ()) <= 2)
449     ;   // TODO!
450   else
451     {
452       r = s->flag ().extent (X_AXIS);
453     }
454   return r;
455 }
456
457
458 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
459
460
461 GLUE_SCORE_ELEMENT(Stem,brew_molecule);
462
463 SCM
464 Stem::member_brew_molecule () const
465 {
466   Molecule mol;
467
468   Staff_symbol_referencer_interface si (first_head ());
469   
470   Real y1 = si.position_f();
471   Real y2 = stem_end_position ();
472   
473   Interval stem_y(y1,y2);
474   stem_y.unite (Interval (y2,y1));
475
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);
481   
482   if (!invisible_b ())
483     {
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);
488     }
489
490   if (!beam_l () && abs (flag_i ()) > 2)
491     {
492       Molecule fl = flag ();
493       fl.translate_axis(stem_y[get_direction ()]*dy, Y_AXIS);
494       mol.add_molecule (fl);
495     }
496
497   return mol.create_scheme();
498 }
499
500 Real
501 Stem::off_callback (Score_element const* se, Axis)
502 {
503   Stem *st = dynamic_cast<Stem*> ((Score_element*)se);
504
505   Real r=0;
506   if (Note_head * f = st->first_head ())
507     {
508       Interval head_wid(0, f->extent (X_AXIS).length ());
509
510       if (to_boolean (st->get_elt_property ("stem-centered")))
511         return head_wid.center ();
512       
513       Real rule_thick = st->paper_l ()->get_var ("stemthickness");
514       Direction d = st->get_direction ();
515       r = head_wid[d] - d * rule_thick ;
516     }
517   return r;
518 }
519
520
521
522 Beam*
523 Stem::beam_l ()const
524 {
525   SCM b=  get_elt_pointer ("beam");
526   return dynamic_cast<Beam*> (unsmob_element (b));
527 }
528
529
530 // ugh still very long.
531 Stem_info
532 Stem::calc_stem_info () const
533 {
534   assert (beam_l ());
535
536   Direction beam_dir = Directional_element_interface (beam_l ()).get ();
537   if (!beam_dir)
538     {
539       programming_error ("Beam dir not set.");
540       beam_dir = UP;
541     }
542     
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 ();
549
550   Stem_info info; 
551   info.idealy_f_ = chord_start_f ();
552
553   // for simplicity, we calculate as if dir == UP
554   info.idealy_f_ *= beam_dir;
555   SCM grace_prop = get_elt_property ("grace");
556
557   bool grace_b = to_boolean (grace_prop);
558   
559   Array<Real> a;
560   SCM s;
561   String type_str = grace_b ? "grace-" : "";
562   
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)));
567
568
569   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
570   s = scm_eval (ly_symbol2scm ((type_str + "beamed-stem-length").ch_C()));
571
572   a.clear();
573   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
574     a.push (gh_scm2double (gh_car (q)));
575
576   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
577
578   if (!beam_dir || (beam_dir == Directional_element_interface (this).get ()))
579     /* normal beamed stem */
580     {
581       if (multiplicity)
582         {
583           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
584         }
585       info.miny_f_ = info.idealy_f_;
586       info.maxy_f_ = INT_MAX;
587
588       info.idealy_f_ += stem_length;
589       info.miny_f_ += minimum_length;
590
591       /*
592         lowest beam of (UP) beam must never be lower than second staffline
593
594         Hmm, reference (Wanske?)
595
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.
599         
600       */
601       bool no_extend_b = to_boolean (get_elt_property ("no-stem-extend"));
602       if (!grace_b && !no_extend_b)
603         {
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
607            */
608           info.miny_f_ =
609             info.miny_f_ >? 0
610             >? (- 2 * half_space - thick
611                 + (multiplicity > 0) * thick
612                 + interbeam_f * (multiplicity - 1));
613         }
614     }
615   else
616     /* knee */
617     {
618       info.idealy_f_ -= thick;
619       info.maxy_f_ = info.idealy_f_;
620       info.miny_f_ = -INT_MAX;
621
622       info.idealy_f_ -= stem_length;
623       info.maxy_f_ -= minimum_length;
624     }
625   
626   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
627
628   s = beam_l ()->get_elt_property ("shorten");
629   if (gh_number_p (s))
630     info.idealy_f_ -= gh_scm2double (s);
631
632   Real interstaff_f = -beam_dir* calc_interstaff_dist (this, beam_l ());
633
634   info.idealy_f_ += interstaff_f;
635   info.miny_f_ += interstaff_f;
636   info.maxy_f_ += interstaff_f ;
637
638   return info;
639 }
640