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