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