]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
a7fe2500b3880625a2255b113cc89edcacf9a020
[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::get (me);
98
99   if (!d)
100     {
101        d = get_default_dir (me);
102        // urg, AAARGH!
103        Directional_element_interface::set (me, 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   return  Pointer_group_interface::count (me, "heads");
154 }
155
156 /*
157   The note head which forms one end of the stem.  
158  */
159 Score_element*
160 Stem::first_head (Score_element*me)
161 {
162   return extremal_heads (me)[-get_direction (me)];
163 }
164
165 /*
166   START is part where stem reaches `last' head. 
167  */
168 Drul_array<Score_element*>
169 Stem::extremal_heads (Score_element*me) 
170 {
171   const int inf = 1000000;
172   Drul_array<int> extpos;
173   extpos[DOWN] = inf;
174   extpos[UP] = -inf;  
175   
176   Drul_array<Score_element *> exthead;
177   exthead[LEFT] = exthead[RIGHT] =0;
178   
179   for (SCM s = me->get_elt_property ("heads"); gh_pair_p (s); s = gh_cdr (s))
180     {
181       Score_element * n = unsmob_element (gh_car (s));
182
183       
184       int p = int(Staff_symbol_referencer::position_f (n));
185
186       Direction d = LEFT;
187       do {
188       if (d* p > d* extpos[d])
189         {
190           exthead[d] = n;
191           extpos[d] = p;
192         }
193       } while (flip (&d) != DOWN);
194     }
195
196   return exthead;
197 }
198
199 void
200 Stem::add_head (Score_element*me, Score_element *n)
201 {
202   n->set_elt_property ("stem", me->self_scm ());
203   n->add_dependency (me);
204
205   if (Note_head::has_interface (n))
206     {
207       Pointer_group_interface::add_element (me, "heads",n);
208     }
209   else
210     {
211       n->set_elt_property ("rest", n->self_scm ());
212     }
213 }
214
215 bool
216 Stem::invisible_b (Score_element*me)
217 {
218   return !(heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
219 }
220
221 int
222 Stem::get_center_distance (Score_element*me, Direction d)
223 {
224   int staff_center = 0;
225   int distance = (int) (d*(head_positions(me)[d] - staff_center));
226   return distance >? 0;
227 }
228
229 Direction
230 Stem::get_default_dir (Score_element*me) 
231 {
232   int du = get_center_distance (me,UP);
233   int dd = get_center_distance (me,DOWN);
234
235   if (sign (dd - du))
236     return Direction (sign (dd -du));
237
238   return to_dir (me->get_elt_property ("default-neutral-direction"));
239 }
240
241 /*
242   ugh. A is used for different purposes. This functionality should be
243   moved into scheme at some point to get rid of the silly
244   conversions. (but lets wait till we have namespaces in SCM)
245  */
246 Real
247 Stem::get_default_stem_end_position (Score_element*me) 
248 {
249   bool grace_b = to_boolean (me->get_elt_property ("grace"));
250   String type_str = grace_b ? "grace-" : "";
251   SCM s;
252   Array<Real> a;
253
254   Real length_f = 0.;
255   SCM scm_len = me->get_elt_property("length");
256   if (gh_number_p (scm_len))
257     {
258       length_f = gh_scm2double (scm_len);
259     }
260   else
261     {
262       s = me->get_elt_property("lengths");
263       for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
264         a.push (gh_scm2double (gh_car (q)));
265                 
266       // stem uses half-spaces
267       length_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
268     }
269
270
271   a.clear ();
272   s = me->get_elt_property ("stem-shorten");
273   for (SCM q = s; gh_pair_p (q); q = gh_cdr (q))
274     a.push (gh_scm2double (gh_car (q)));
275
276
277   // stem uses half-spaces
278
279   // fixme: use gh_list_ref () iso. array[]
280   Real shorten_f = a[((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
281
282   /* URGURGURG
283      'set-default-stemlen' sets direction too
284    */
285   Direction dir = get_direction (me);
286   if (!dir)
287     {
288       dir = get_default_dir (me);
289       Directional_element_interface::set (me, dir);
290     }
291   
292   /* 
293     stems in unnatural (forced) direction should be shortened, 
294     according to [Roush & Gourlay]
295    */
296   if (((int)chord_start_f (me))
297       && (get_direction (me) != get_default_dir (me)))
298     length_f -= shorten_f;
299
300
301    Real st = head_positions(me)[dir] + dir * length_f;
302   
303    bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
304     if (!grace_b && !no_extend_b && dir * st < 0)
305       st = 0.0;
306
307   return st;
308 }
309
310 /*
311   FIXME: wrong name
312  */
313 int
314 Stem::flag_i (Score_element*me) 
315 {
316   SCM s = me->get_elt_property ("duration-log");
317   return  (gh_number_p (s)) ? gh_scm2int (s) : 2;
318 }
319
320 void
321 Stem::position_noteheads (Score_element*me)
322 {
323   if (!heads_i (me))
324     return;
325   
326   Link_array<Score_element> heads =
327     Pointer_group_interface__extract_elements (me, (Score_element*)0, "heads");
328
329   heads.sort (compare_position);
330   Direction dir =get_direction (me);
331   
332   if (dir < 0)
333     heads.reverse ();
334
335
336   Score_element *hed = support_head (me);
337   Real w = hed->extent (hed, X_AXIS)[dir];
338   for (int i=0; i < heads.size (); i++)
339     {
340       heads[i]->translate_axis (w - heads[i]->extent (heads[i], 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 (heads[i], 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,1);
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 (scm_sloppy_memq (scmdir, dirlist) == SCM_BOOL_F)
404         {
405           dirlist = gh_cons (scmdir, dirlist);
406           col->set_elt_property ("dir-list", dirlist);
407         }
408     }
409 }
410
411 Molecule
412 Stem::flag (Score_element*me)
413 {
414   String style;
415   SCM st = me->get_elt_property ("flag-style");
416   if ( gh_string_p (st))
417     {
418       style = ly_scm2string (st);
419     }
420
421   char c = (get_direction (me) == UP) ? 'u' : 'd';
422   Molecule m = me->lookup_l ()->afm_find (String ("flags-") + to_str (c) + 
423                                       to_str (flag_i (me)));
424   if (!style.empty_b ())
425     m.add_molecule(me->lookup_l ()->afm_find (String ("flags-") + to_str (c) + style));
426   return m;
427 }
428
429 MAKE_SCHEME_CALLBACK(Stem,dim_callback,2);
430 SCM
431 Stem::dim_callback (SCM e, SCM )
432 {
433    Score_element *se = unsmob_element (e);
434   Interval r (0, 0);
435   if (unsmob_element (se->get_elt_property ("beam")) || abs (flag_i (se)) <= 2)
436     ;   // TODO!
437   else
438     {
439       r = flag (se).extent (X_AXIS);
440     }
441   return ly_interval2scm ( r);
442 }
443
444
445 const Real ANGLE = 20* (2.0*M_PI/360.0); // ugh! Should be settable.
446
447
448 MAKE_SCHEME_CALLBACK(Stem,brew_molecule,1);
449
450 SCM
451 Stem::brew_molecule (SCM smob) 
452 {
453   Score_element*me = unsmob_element (smob);
454   Molecule mol;
455   Direction d = get_direction (me);
456   
457   
458   Real y1 = Staff_symbol_referencer::position_f (first_head (me));
459   Real y2 = stem_end_position (me);
460   
461   Interval stem_y(y1,y2);
462   stem_y.unite (Interval (y2,y1));
463
464   Real dy = Staff_symbol_referencer::staff_space (me)/2.0;
465   Real head_wid = 0;
466   
467   if (Score_element *hed = support_head (me))
468     head_wid = hed->extent (hed,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 = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
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 MAKE_SCHEME_CALLBACK(Stem,off_callback,2);
490 SCM
491 Stem::off_callback (SCM element_smob, SCM )
492 {
493   Score_element *me = unsmob_element (element_smob);
494   
495   Real r=0;
496   if (Score_element * f = first_head (me))
497     {
498       Interval head_wid(0, f->extent (f,X_AXIS).length ());
499
500       if (to_boolean (me->get_elt_property ("stem-centered")))
501         return gh_double2scm ( head_wid.center ());
502       
503       Real rule_thick = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
504       Direction d = get_direction (me);
505       r = head_wid[d] - d * rule_thick ;
506     }
507   return gh_double2scm (r);
508 }
509
510
511
512 Score_element*
513 Stem::beam_l (Score_element*me)
514 {
515   SCM b=  me->get_elt_property ("beam");
516   return unsmob_element (b);
517 }
518
519
520 // ugh still very long.
521 Stem_info
522 Stem::calc_stem_info (Score_element*me) 
523 {
524   Score_element * beam = beam_l (me);
525
526   Direction beam_dir = Directional_element_interface::get (beam);
527   if (!beam_dir)
528     {
529       programming_error ("Beam dir not set.");
530       beam_dir = UP;
531     }
532     
533
534   Real staff_space = Staff_symbol_referencer::staff_space (me);
535   Real half_space = staff_space / 2;
536   int multiplicity = Beam::get_multiplicity (beam);
537
538
539   SCM space_proc = beam->get_elt_property ("space-function");
540   SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
541   Real interbeam_f = gh_scm2double (space) * staff_space;
542
543   Real thick = gh_scm2double (beam->get_elt_property ("thickness"));
544   Stem_info info; 
545   info.idealy_f_ = chord_start_f (me);
546
547   // for simplicity, we calculate as if dir == UP
548   info.idealy_f_ *= beam_dir;
549   SCM grace_prop = me->get_elt_property ("grace");
550
551   bool grace_b = to_boolean (grace_prop);
552   
553   Array<Real> a;
554   SCM s;
555   
556   s = me->get_elt_property("beamed-minimum-lengths");
557   a.clear ();
558   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
559     a.push (gh_scm2double (gh_car (q)));
560
561
562   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
563   s = me->get_elt_property ("beamed-lengths");
564
565   a.clear();
566   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
567     a.push (gh_scm2double (gh_car (q)));
568
569   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
570
571   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
572     /* normal beamed stem */
573     {
574       if (multiplicity)
575         {
576           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
577         }
578       info.miny_f_ = info.idealy_f_;
579       info.maxy_f_ = INT_MAX;
580
581       info.idealy_f_ += stem_length;
582       info.miny_f_ += minimum_length;
583
584       /*
585         lowest beam of (UP) beam must never be lower than second staffline
586
587         Hmm, reference (Wanske?)
588
589         Although this (additional) rule is probably correct,
590         I expect that highest beam (UP) should also never be lower
591         than middle staffline, just as normal stems.
592         
593       */
594       bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
595       if (!grace_b && !no_extend_b)
596         {
597           /* highest beam of (UP) beam must never be lower than middle
598              staffline
599              lowest beam of (UP) beam must never be lower than second staffline
600            */
601           info.miny_f_ =
602             info.miny_f_ >? 0
603             >? (- 2 * half_space - thick
604                 + (multiplicity > 0) * thick
605                 + interbeam_f * (multiplicity - 1));
606         }
607     }
608   else
609     /* knee */
610     {
611       info.idealy_f_ -= thick;
612       info.maxy_f_ = info.idealy_f_;
613       info.miny_f_ = -INT_MAX;
614
615       info.idealy_f_ -= stem_length;
616       info.maxy_f_ -= minimum_length;
617     }
618   
619   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
620
621   s = beam->get_elt_property ("shorten");
622   if (gh_number_p (s))
623     info.idealy_f_ -= gh_scm2double (s);
624
625   Real interstaff_f = -beam_dir* calc_interstaff_dist (dynamic_cast<Item*> (me), dynamic_cast<Spanner*> (beam));
626
627   info.idealy_f_ += interstaff_f;
628   info.miny_f_ += interstaff_f;
629   info.maxy_f_ += interstaff_f ;
630
631   return info;
632 }
633
634 bool
635 Stem::has_interface (Score_element*m)
636 {
637   return m && m->has_interface (ly_symbol2scm ("stem-interface"));
638 }
639
640 void
641 Stem::set_interface (Score_element*me)
642 {    
643   me->set_interface (ly_symbol2scm ("stem-interface"));
644 }