]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
patch::: 1.3.93.jcn2
[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   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 = me->get_elt_property("lengths");
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 = me->get_elt_property ("stem-shorten");
274   for (SCM q = s; gh_pair_p (q); 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::set (me, 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,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 (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,1);
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 = 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 axis)
492 {
493   Score_element *me = unsmob_element (element_smob);
494   Axis a = (Axis) gh_scm2int (axis);
495   Real r=0;
496   if (Score_element * f = first_head (me))
497     {
498       Interval head_wid(0, f->extent (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_elt_property ("heads", SCM_EOL);
644   me->add_offset_callback ( Stem_off_callback_proc, X_AXIS);
645   me->set_interface (ly_symbol2scm ("stem-interface"));
646 }