]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
release: 1.3.94
[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 MAKE_SCHEME_CALLBACK(Stem,dim_callback,2);
433 SCM
434 Stem::dim_callback (SCM e, SCM )
435 {
436    Score_element *se = unsmob_element (e);
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 ly_interval2scm ( 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,1);
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 MAKE_SCHEME_CALLBACK(Stem,off_callback,2);
492 SCM
493 Stem::off_callback (SCM element_smob, SCM axis)
494 {
495   Score_element *me = unsmob_element (element_smob);
496
497   Real r=0;
498   if (Score_element * f = first_head (me))
499     {
500       Interval head_wid(0, f->extent (X_AXIS).length ());
501
502       if (to_boolean (me->get_elt_property ("stem-centered")))
503         return gh_double2scm ( head_wid.center ());
504       
505       Real rule_thick = gh_scm2double (me->get_elt_property ("thickness")) * me->paper_l ()->get_var ("stafflinethickness");
506       Direction d = get_direction (me);
507       r = head_wid[d] - d * rule_thick ;
508     }
509   return gh_double2scm (r);
510 }
511
512
513
514 Score_element*
515 Stem::beam_l (Score_element*me)
516 {
517   SCM b=  me->get_elt_property ("beam");
518   return unsmob_element (b);
519 }
520
521
522 // ugh still very long.
523 Stem_info
524 Stem::calc_stem_info (Score_element*me) 
525 {
526   Score_element * beam = beam_l (me);
527
528   Direction beam_dir = Directional_element_interface::get (beam);
529   if (!beam_dir)
530     {
531       programming_error ("Beam dir not set.");
532       beam_dir = UP;
533     }
534     
535
536   Real staff_space = Staff_symbol_referencer::staff_space (me);
537   Real half_space = staff_space / 2;
538   int multiplicity = Beam::get_multiplicity (beam);
539
540
541   SCM space_proc = beam->get_elt_property ("space-function");
542   SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
543   Real interbeam_f = gh_scm2double (space) * staff_space;
544
545   Real thick = gh_scm2double (beam->get_elt_property ("thickness"));
546   Stem_info info; 
547   info.idealy_f_ = chord_start_f (me);
548
549   // for simplicity, we calculate as if dir == UP
550   info.idealy_f_ *= beam_dir;
551   SCM grace_prop = me->get_elt_property ("grace");
552
553   bool grace_b = to_boolean (grace_prop);
554   
555   Array<Real> a;
556   SCM s;
557   
558   s = me->get_elt_property("beamed-minimum-lengths");
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
564   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
565   s = me->get_elt_property ("beamed-lengths");
566
567   a.clear();
568   for (SCM q = s; q != SCM_EOL; q = gh_cdr (q))
569     a.push (gh_scm2double (gh_car (q)));
570
571   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
572
573   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
574     /* normal beamed stem */
575     {
576       if (multiplicity)
577         {
578           info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
579         }
580       info.miny_f_ = info.idealy_f_;
581       info.maxy_f_ = INT_MAX;
582
583       info.idealy_f_ += stem_length;
584       info.miny_f_ += minimum_length;
585
586       /*
587         lowest beam of (UP) beam must never be lower than second staffline
588
589         Hmm, reference (Wanske?)
590
591         Although this (additional) rule is probably correct,
592         I expect that highest beam (UP) should also never be lower
593         than middle staffline, just as normal stems.
594         
595       */
596       bool no_extend_b = to_boolean (me->get_elt_property ("no-stem-extend"));
597       if (!grace_b && !no_extend_b)
598         {
599           /* highest beam of (UP) beam must never be lower than middle
600              staffline
601              lowest beam of (UP) beam must never be lower than second staffline
602            */
603           info.miny_f_ =
604             info.miny_f_ >? 0
605             >? (- 2 * half_space - thick
606                 + (multiplicity > 0) * thick
607                 + interbeam_f * (multiplicity - 1));
608         }
609     }
610   else
611     /* knee */
612     {
613       info.idealy_f_ -= thick;
614       info.maxy_f_ = info.idealy_f_;
615       info.miny_f_ = -INT_MAX;
616
617       info.idealy_f_ -= stem_length;
618       info.maxy_f_ -= minimum_length;
619     }
620   
621   info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
622
623   s = beam->get_elt_property ("shorten");
624   if (gh_number_p (s))
625     info.idealy_f_ -= gh_scm2double (s);
626
627   Real interstaff_f = -beam_dir* calc_interstaff_dist (dynamic_cast<Item*> (me), dynamic_cast<Spanner*> (beam));
628
629   info.idealy_f_ += interstaff_f;
630   info.miny_f_ += interstaff_f;
631   info.maxy_f_ += interstaff_f ;
632
633   return info;
634 }
635
636 bool
637 Stem::has_interface (Score_element*m)
638 {
639   return m && m->has_interface (ly_symbol2scm ("stem-interface"));
640 }
641
642 void
643 Stem::set_interface (Score_element*me)
644 {    
645   me->set_interface (ly_symbol2scm ("stem-interface"));
646 }