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