]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.3.90
[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);
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 = 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 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 = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
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::get (beam);
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   int multiplicity = Beam::get_multiplicity (beam);
534
535
536   SCM space_proc = beam->get_elt_property ("space-function");
537   SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
538   Real interbeam_f = gh_scm2double (space) * staff_space;
539
540   Real thick = gh_scm2double (beam->get_elt_property ("thickness"));
541   Stem_info info; 
542   info.idealy_f_ = chord_start_f (me);
543
544   // for simplicity, we calculate as if dir == UP
545   info.idealy_f_ *= beam_dir;
546   SCM grace_prop = me->get_elt_property ("grace");
547
548   bool grace_b = to_boolean (grace_prop);
549   
550   Array<Real> a;
551   SCM s;
552   
553   s = me->get_elt_property("beamed-minimum-lengths");
554   a.clear ();
555   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
556     a.push (gh_scm2double (gh_car (q)));
557
558
559   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
560   s = me->get_elt_property ("beamed-lengths");
561
562   a.clear();
563   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
564     a.push (gh_scm2double (gh_car (q)));
565
566   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
567
568   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
569     /* normal beamed stem */
570     {
571       if (multiplicity)
572         {
573           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
574         }
575       info.miny_f_ = info.idealy_f_;
576       info.maxy_f_ = INT_MAX;
577
578       info.idealy_f_ += stem_length;
579       info.miny_f_ += minimum_length;
580
581       /*
582         lowest beam of (UP) beam must never be lower than second staffline
583
584         Hmm, reference (Wanske?)
585
586         Although this (additional) rule is probably correct,
587         I expect that highest beam (UP) should also never be lower
588         than middle staffline, just as normal stems.
589         
590       */
591       bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
592       if (!grace_b && !no_extend_b)
593         {
594           /* highest beam of (UP) beam must never be lower than middle
595              staffline
596              lowest beam of (UP) beam must never be lower than second staffline
597            */
598           info.miny_f_ =
599             info.miny_f_ >? 0
600             >? (- 2 * half_space - thick
601                 + (multiplicity > 0) * thick
602                 + interbeam_f * (multiplicity - 1));
603         }
604     }
605   else
606     /* knee */
607     {
608       info.idealy_f_ -= thick;
609       info.maxy_f_ = info.idealy_f_;
610       info.miny_f_ = -INT_MAX;
611
612       info.idealy_f_ -= stem_length;
613       info.maxy_f_ -= minimum_length;
614     }
615   
616   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
617
618   s = beam->get_elt_property ("shorten");
619   if (gh_number_p (s))
620     info.idealy_f_ -= gh_scm2double (s);
621
622   Real interstaff_f = -beam_dir* calc_interstaff_dist (dynamic_cast<Item*> (me), dynamic_cast<Spanner*> (beam));
623
624   info.idealy_f_ += interstaff_f;
625   info.miny_f_ += interstaff_f;
626   info.maxy_f_ += interstaff_f ;
627
628   return info;
629 }
630
631 bool
632 Stem::has_interface (Score_element*m)
633 {
634   return m && m->has_interface (ly_symbol2scm ("stem-interface"));
635 }
636
637 void
638 Stem::set_interface (Score_element*me)
639 {    
640   me->set_elt_property ("heads", SCM_EOL);
641   me->add_offset_callback ( &Stem::off_callback, X_AXIS);
642   me->set_interface (ly_symbol2scm ("stem-interface"));
643 }