]> git.donarmstrong.com Git - lilypond.git/blob - lily/score-element.cc
26a62293fcec8cf03c43014bc331b8a6876d3323
[lilypond.git] / lily / score-element.cc
1 /*
2   score-elem.cc -- implement Score_element
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include <string.h>
11 #include <math.h>
12
13 #include "input-smob.hh"
14 #include "libc-extension.hh"
15 #include "group-interface.hh"
16 #include "misc.hh"
17 #include "paper-score.hh"
18 #include "paper-def.hh"
19 #include "lookup.hh"
20 #include "molecule.hh"
21 #include "score-element.hh"
22 #include "debug.hh"
23 #include "spanner.hh"
24 #include "line-of-score.hh"
25 #include "item.hh"
26 #include "paper-column.hh"
27 #include "molecule.hh"
28 #include "misc.hh"
29 #include "paper-outputter.hh"
30 #include "dimension-cache.hh"
31 #include "side-position-interface.hh"
32 #include "item.hh"
33
34 #include "ly-smobs.icc"
35
36 /*
37 TODO:
38
39 remove dynamic_cast<Spanner,Item> and put this code into respective
40   subclass.
41 */
42
43
44 #define INFINITY_MSG "Infinity or NaN encountered"
45
46 Score_element::Score_element(SCM basicprops)
47 {
48   /*
49     fixme: default should be no callback.
50    */
51   set_extent_callback (molecule_extent, X_AXIS);
52   set_extent_callback (molecule_extent, Y_AXIS);    
53
54   pscore_l_=0;
55   lookup_l_ =0;
56   status_i_ = 0;
57   original_l_ = 0;
58   immutable_property_alist_ =  basicprops;
59   mutable_property_alist_ = SCM_EOL;
60
61   smobify_self ();
62   set_elt_property ("dependencies", SCM_EOL);
63
64   if (get_elt_property ("interfaces") == SCM_UNDEFINED)
65     set_elt_property ("interfaces", SCM_EOL);
66 }
67
68
69 Score_element::Score_element (Score_element const&s)
70    : dim_cache_ (s.dim_cache_)
71 {
72   original_l_ =(Score_element*) &s;
73   immutable_property_alist_ = s.immutable_property_alist_;
74   mutable_property_alist_ = SCM_EOL;
75   
76   status_i_ = s.status_i_;
77   lookup_l_ = s.lookup_l_;
78   pscore_l_ = s.pscore_l_;
79
80   smobify_self ();
81 }
82
83 Score_element::~Score_element()
84 {
85   /*
86     do nothing scm-ish and no unprotecting here.
87    */
88 }
89
90
91 SCM
92 Score_element::get_elt_property (const char *nm) const
93 {
94   SCM sym = ly_symbol2scm (nm);
95   return get_elt_property (sym);
96 }
97
98 SCM
99 Score_element::get_elt_property (SCM sym) const
100 {
101   SCM s = scm_sloppy_assq(sym, mutable_property_alist_);
102   if (s != SCM_BOOL_F)
103     return gh_cdr (s);
104
105   s = scm_sloppy_assq (sym, immutable_property_alist_);
106   return (s == SCM_BOOL_F) ? SCM_EOL : gh_cdr (s); 
107 }
108
109 /*
110   Remove the value associated with KEY, and return it. The result is
111   that a next call will yield SCM_UNDEFINED (and not the underlying
112   `basic' property.
113 */
114 SCM
115 Score_element::remove_elt_property (const char* key)
116 {
117   SCM val = get_elt_property (key);
118   if (val != SCM_EOL)
119     set_elt_property (key, SCM_EOL);
120   return val;
121 }
122
123 void
124 Score_element::set_elt_property (const char* k, SCM v)
125 {
126   SCM s = ly_symbol2scm (k);
127   set_elt_property (s, v);
128 }
129
130 /*
131   Puts the k, v in the immutable_property_alist_, which is convenient for
132   storing variables that are needed during the breaking process. (eg.
133   Line_of_score::rank : int )
134  */
135 void
136 Score_element::set_immutable_elt_property (const char*k, SCM v)
137 {
138   SCM s = ly_symbol2scm (k);
139   set_immutable_elt_property (s, v);
140 }
141
142 void
143 Score_element::set_immutable_elt_property (SCM s, SCM v)
144 {
145   immutable_property_alist_ = gh_cons (gh_cons (s,v), mutable_property_alist_);
146   mutable_property_alist_ = scm_assq_remove_x (mutable_property_alist_, s);
147 }
148 void
149 Score_element::set_elt_property (SCM s, SCM v)
150 {
151   mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
152 }
153
154
155 Interval
156 Score_element::molecule_extent (Score_element *s, Axis a)
157 {
158   Molecule m = s->get_molecule ();
159   return m.extent(a);
160 }
161
162 Interval
163 Score_element::preset_extent (Score_element  *s , Axis a)
164 {
165   SCM ext = s->get_elt_property ((a == X_AXIS)
166                                  ? "extent-X"
167                                  : "extent-Y");
168   
169   if (gh_pair_p (ext))
170     {
171       Real l = gh_scm2double (gh_car (ext));
172       Real r = gh_scm2double (gh_cdr (ext));
173       l *= s->paper_l ()->get_var ("staffspace");
174       r *= s->paper_l ()->get_var ("staffspace");
175       return Interval (l, r);
176     }
177   
178   return Interval ();
179 }
180
181
182
183 Paper_def*
184 Score_element::paper_l ()  const
185 {
186  return pscore_l_ ? pscore_l_->paper_l_ : 0;
187 }
188
189 Lookup const *
190 Score_element::lookup_l () const
191 {
192   /*
193     URG junkthis, caching is clumsy.
194    */
195   if (!lookup_l_)
196     {
197       Score_element * urg = (Score_element*)this;
198       SCM sz = urg->remove_elt_property ("font-size");
199       int i = (gh_number_p (sz))
200         ? gh_scm2int  (sz)
201         : 0;
202
203       urg->lookup_l_ =  (Lookup*)pscore_l_->paper_l_->lookup_l (i);
204     }
205   return lookup_l_;
206 }
207
208 void
209 Score_element::add_processing()
210 {
211   assert (status_i_ >=0);
212   if (status_i_)
213     return;
214   status_i_ ++;
215
216   do_add_processing();
217 }
218
219 void
220 Score_element::calculate_dependencies (int final, int busy, SCM funcname)
221 {
222   assert (status_i_ >=0);
223
224   if (status_i_ >= final)
225     return;
226
227   if (status_i_== busy)
228     {
229       programming_error ("Element is busy, come back later");
230       return;
231     }
232   
233   status_i_= busy;
234
235   for (SCM d=  get_elt_property ("dependencies"); gh_pair_p (d); d = gh_cdr (d))
236     {
237       unsmob_element (gh_car (d))
238         ->calculate_dependencies (final, busy, funcname);
239     }
240
241   // ughugh.
242   String s = ly_symbol2string (funcname);
243   SCM proc = get_elt_property (s.ch_C());
244   if (gh_procedure_p (proc))
245     gh_call1 (proc, this->self_scm ());
246   
247   status_i_= final;
248
249 }
250
251 Molecule
252 Score_element::get_molecule ()  const
253 {
254   SCM proc = get_elt_property ("molecule-callback");
255
256   SCM mol = SCM_EOL;
257   if (gh_procedure_p (proc)) 
258     mol = gh_apply (proc, gh_list (this->self_scm (), SCM_UNDEFINED));
259
260
261   SCM origin =get_elt_property ("origin");
262   if (!unsmob_input (origin))
263     origin =ly_symbol2scm ("no-origin");
264   
265   if (gh_pair_p (mol))
266     {
267       // ugr.
268         mol = gh_cons (gh_list (origin, gh_car (mol), SCM_UNDEFINED), gh_cdr (mol));
269     }
270
271   
272   return create_molecule (mol);
273 }
274
275
276 /*
277   
278   VIRTUAL STUBS
279
280  */
281 void
282 Score_element::do_break_processing()
283 {
284 }
285
286
287 void
288 Score_element::do_add_processing()
289 {
290 }
291
292
293 MAKE_SCHEME_CALLBACK(Score_element,brew_molecule)
294
295 /*
296   ugh.
297  */  
298 SCM
299 Score_element::brew_molecule (SCM smob) 
300 {
301   Score_element * sc = unsmob_element (smob);
302   SCM glyph = sc->get_elt_property ("glyph");
303   if (gh_string_p (glyph))
304     {
305       return sc->lookup_l ()->afm_find (String (ly_scm2string (glyph))).create_scheme ();
306     }
307   else
308     {
309       return SCM_EOL;
310     }
311 }
312
313
314 Line_of_score *
315 Score_element::line_l() const
316 {
317   return 0;
318 }
319
320 void
321 Score_element::add_dependency (Score_element*e)
322 {
323   if (e)
324     {
325       Pointer_group_interface gi (this, "dependencies");
326       gi.add_element (e);
327     }
328   else
329     programming_error ("Null dependency added");
330 }
331
332
333
334
335 /**
336       Do break substitution in S, using CRITERION. Return new value.
337       CRITERION is either a SMOB pointer to the desired line, or a number
338       representing the break direction. Do not modify SRC.
339 */
340 SCM
341 Score_element::handle_broken_smobs (SCM src, SCM criterion)
342 {
343  again:
344   Score_element *sc = unsmob_element (src);
345   if (sc)
346     {
347       if (gh_number_p (criterion))
348         {
349           Item * i = dynamic_cast<Item*> (sc);
350           Direction d = to_dir (criterion);
351           if (i && i->break_status_dir () != d)
352             {
353               Item *br = i->find_prebroken_piece (d);
354               return  (br) ? br->self_scm () : SCM_UNDEFINED;
355             }
356         }
357       else
358         {
359           Line_of_score * line
360             = dynamic_cast<Line_of_score*> (unsmob_element (criterion));
361           if (sc->line_l () != line)
362             {
363               sc = sc->find_broken_piece (line);
364
365             }
366
367           /* now: !sc || (sc && sc->line_l () == line) */
368           if (!sc)
369             return SCM_UNDEFINED;
370
371           /* now: sc && sc->line_l () == line */
372           if (!line
373               || (sc->common_refpoint (line, X_AXIS)
374                   && sc->common_refpoint (line, Y_AXIS)))
375             {
376               return sc->self_scm ();
377             }
378           return SCM_UNDEFINED;
379         }
380     }
381   else if (gh_pair_p (src))
382     {
383       SCM oldcar =gh_car (src);
384       /*
385         UGH! breaks on circular lists.
386       */
387       SCM newcar = handle_broken_smobs (oldcar, criterion);
388       SCM oldcdr = gh_cdr (src);
389       
390       if (newcar == SCM_UNDEFINED
391           && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
392         {
393           /*
394             This is tail-recursion, ie. 
395             
396             return handle_broken_smobs (cdr, criterion);
397
398             We don't want to rely on the compiler to do this.  Without
399             tail-recursion, this easily crashes with a stack overflow.  */
400           src =  oldcdr;
401           goto again;
402         }
403
404       SCM newcdr = handle_broken_smobs (oldcdr, criterion);
405       return gh_cons (newcar, newcdr);
406     }
407   else
408     return src;
409
410   return src;
411 }
412
413 void
414 Score_element::handle_broken_dependencies()
415 {
416   Spanner * s= dynamic_cast<Spanner*> (this);
417   if (original_l_ && s)
418     return;
419
420   if (s)
421     {
422       for (int i = 0;  i< s->broken_into_l_arr_ .size (); i++)
423         {
424           Score_element * sc = s->broken_into_l_arr_[i];
425           Line_of_score * l = sc->line_l ();
426           sc->mutable_property_alist_ =
427             handle_broken_smobs (mutable_property_alist_,
428                                  l ? l->self_scm () : SCM_UNDEFINED);
429         }
430     }
431
432
433   Line_of_score *line = line_l();
434
435   if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
436     {
437       mutable_property_alist_
438         = handle_broken_smobs (mutable_property_alist_,
439                                line ? line->self_scm () : SCM_UNDEFINED);
440     }
441   else if (dynamic_cast <Line_of_score*> (this))
442     {
443       mutable_property_alist_ = handle_broken_smobs (mutable_property_alist_,
444                                             SCM_UNDEFINED);
445     }
446   else
447     {
448       /*
449         This element is `invalid'; it has been removed from all
450         dependencies, so let's junk the element itself.
451
452         do not do this for Line_of_score, since that would remove
453         references to the originals of score-elts, which get then GC'd
454         (a bad thing.)
455       */
456       suicide();
457     }
458 }
459
460 /*
461  Note that we still want references to this element to be
462  rearranged, and not silently thrown away, so we keep pointers
463  like {broken_into_{drul,array}, original}
464 */
465 void
466 Score_element::suicide ()
467 {
468   mutable_property_alist_ = SCM_EOL;
469   immutable_property_alist_ = SCM_EOL;
470   set_extent_callback (0, Y_AXIS);
471   set_extent_callback (0, X_AXIS);
472
473   for (int a= X_AXIS; a <= Y_AXIS; a++)
474     {
475       dim_cache_[a].off_callbacks_.clear ();
476     }
477 }
478
479 void
480 Score_element::handle_prebroken_dependencies()
481 {
482 }
483
484 Score_element*
485 Score_element::find_broken_piece (Line_of_score*) const
486 {
487   return 0;
488 }
489
490 void
491 Score_element::translate_axis (Real y, Axis a)
492 {
493   if (isinf (y) || isnan (y))
494     programming_error (_(INFINITY_MSG));
495   else
496     {
497       dim_cache_[a].offset_ += y;
498     }
499 }  
500
501 Real
502 Score_element::relative_coordinate (Score_element const*refp, Axis a) const
503 {
504   if (refp == this)
505     return 0.0;
506
507   /*
508     We catch PARENT_L_ == nil case with this, but we crash if we did
509     not ask for the absolute coordinate (ie. REFP == nil.)
510     
511    */
512   if (refp == dim_cache_[a].parent_l_)
513     return get_offset (a);
514   else
515     return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
516 }
517
518 Real
519 Score_element::get_offset (Axis a) const
520 {
521   Score_element *me = (Score_element*) this;
522   while (dim_cache_[a].off_callbacks_.size ())
523     {
524       Offset_callback c = dim_cache_[a].off_callbacks_[0];
525       me->dim_cache_[a].off_callbacks_.del (0);
526       Real r =  (*c) (me,a );
527       if (isinf (r) || isnan (r))
528         {
529           programming_error (INFINITY_MSG);
530           r = 0.0;
531         }
532       me->dim_cache_[a].offset_ +=r;
533     }
534   return dim_cache_[a].offset_;
535 }
536
537
538 Interval
539 Score_element::point_dimension_callback (Score_element* , Axis)
540 {
541   return Interval (0,0);
542 }
543
544 bool
545 Score_element::empty_b (Axis a)const
546 {
547   return !dim_cache_[a].extent_callback_l_;
548 }
549
550 Interval
551 Score_element::extent (Axis a) const
552 {
553   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
554   if (!d->extent_callback_l_)
555     {
556       d->dim_.set_empty ();
557     }
558   else if (!d->valid_b_)
559     {
560       d->dim_= (*d->extent_callback_l_ ) ((Score_element*)this, a);
561       d->valid_b_ = true;
562     }
563
564   Interval ext = d->dim_;
565   
566   if (empty_b (a)) 
567     return ext;
568
569   SCM extra = get_elt_property (a == X_AXIS
570                                 ? "extra-extent-X"
571                                 : "extra-extent-Y");
572
573   /*
574     signs ?
575    */
576   Real s = paper_l ()->get_var ("staffspace");
577   if (gh_pair_p (extra))
578     {
579       ext[BIGGER] +=  s * gh_scm2double (gh_cdr (extra));
580       ext[SMALLER] +=  s * gh_scm2double (gh_car (extra));
581     }
582   
583   extra = get_elt_property (a == X_AXIS
584                                 ? "minimum-extent-X"
585                                 : "minimum-extent-Y");
586   if (gh_pair_p (extra))
587     {
588       ext.unite (Interval (s * gh_scm2double (gh_car (extra)),
589                            s * gh_scm2double (gh_cdr (extra))));
590     }
591   
592   return ext;
593 }
594
595
596 Score_element*
597 Score_element::parent_l (Axis a) const
598 {
599   return  dim_cache_[a].parent_l_;
600 }
601
602 Score_element * 
603 Score_element::common_refpoint (Score_element const* s, Axis a) const
604 {
605   /*
606     I don't like the quadratic aspect of this code, but I see no other
607     way. The largest chain of parents might be 10 high or so, so
608     it shouldn't be a real issue. */
609   for (Score_element const *c = this; c; c = c->dim_cache_[a].parent_l_)
610     for (Score_element const * d = s; d; d = d->dim_cache_[a].parent_l_)
611       if (d == c)
612         return (Score_element*)d;
613
614   return 0;
615 }
616
617
618 Score_element *
619 Score_element::common_refpoint (SCM elist, Axis a) const
620 {
621   Score_element * common = (Score_element*) this;
622   for (; gh_pair_p (elist); elist = gh_cdr (elist))
623     {
624       Score_element * s = unsmob_element (gh_car (elist));
625       if (s)
626         common = common->common_refpoint (s, a);
627     }
628
629   return common;
630 }
631
632 char const *
633 Score_element::name () const
634 {
635   return classname (this);
636 }
637
638 void
639 Score_element::add_offset_callback (Offset_callback cb, Axis a)
640 {
641   dim_cache_[a].off_callbacks_.push (cb);
642 }
643
644 bool
645 Score_element::has_extent_callback_b (Extent_callback cb, Axis a)const
646 {
647   return cb == dim_cache_[a].extent_callback_l_;
648 }
649
650
651 bool
652 Score_element::has_extent_callback_b (Axis a) const
653 {
654   return dim_cache_[a].extent_callback_l_;
655 }
656
657 bool
658 Score_element::has_offset_callback_b (Offset_callback cb, Axis a)const
659 {
660   for (int i= dim_cache_[a].off_callbacks_.size (); i--;)
661     {
662       if (dim_cache_[a].off_callbacks_[i] == cb)
663         return true;
664     }
665   return false;
666 }
667
668 void
669 Score_element::set_extent_callback (Dim_cache_callback dc, Axis a)
670 {
671   dim_cache_[a].extent_callback_l_ = dc ;
672 }
673
674                                     
675 void
676 Score_element::set_parent (Score_element *g, Axis a)
677 {
678   dim_cache_[a].parent_l_ = g;
679 }
680
681 MAKE_SCHEME_CALLBACK(Score_element,fixup_refpoint);
682 SCM
683 Score_element::fixup_refpoint (SCM smob)
684 {
685   Score_element *me = unsmob_element (smob);
686   for (int a = X_AXIS; a < NO_AXES; a ++)
687     {
688       Axis ax = (Axis)a;
689       Score_element * parent = me->parent_l (ax);
690
691       if (!parent)
692         continue;
693       
694       if (parent->line_l () != me->line_l () && me->line_l ())
695         {
696           Score_element * newparent = parent->find_broken_piece (me->line_l ());
697           me->set_parent (newparent, ax);
698         }
699
700       if (Item * i  = dynamic_cast<Item*> (me))
701         {
702           Item *parenti = dynamic_cast<Item*> (parent);
703
704           if (parenti && i)
705             {
706               Direction  my_dir = i->break_status_dir () ;
707               if (my_dir!= parenti->break_status_dir())
708                 {
709                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
710                   me->set_parent (newparent, ax);
711                 }
712             }
713         }
714     }
715   return smob;
716 }
717
718
719
720 /****************************************************
721   SMOB funcs
722  ****************************************************/
723
724
725 IMPLEMENT_UNSMOB(Score_element, element);
726 IMPLEMENT_SMOBS(Score_element);
727 IMPLEMENT_DEFAULT_EQUAL_P(Score_element);
728
729 SCM
730 Score_element::mark_smob (SCM ses)
731 {
732   Score_element * s = (Score_element*) SCM_CELL_WORD_1(ses);
733   scm_gc_mark (s->immutable_property_alist_);
734   scm_gc_mark (s->mutable_property_alist_);
735   
736   if (s->parent_l (Y_AXIS))
737     scm_gc_mark (s->parent_l (Y_AXIS)->self_scm ());
738   if (s->parent_l (X_AXIS))
739     scm_gc_mark (s->parent_l (X_AXIS)->self_scm ());
740
741   if (s->original_l_)
742     scm_gc_mark (s->original_l_->self_scm ());
743   return s->do_derived_mark ();
744 }
745
746 int
747 Score_element::print_smob (SCM s, SCM port, scm_print_state *)
748 {
749   Score_element *sc = (Score_element *) gh_cdr (s);
750      
751   scm_puts ("#<Score_element ", port);
752   scm_puts ((char *)sc->name (), port);
753
754   /*
755     don't try to print properties, that is too much hassle.
756    */
757   scm_puts (" >", port);
758   return 1;
759 }
760
761 SCM
762 Score_element::do_derived_mark ()
763 {
764   return SCM_EOL;
765 }
766
767
768 SCM
769 ly_set_elt_property (SCM elt, SCM sym, SCM val)
770 {
771   Score_element * sc = unsmob_element (elt);
772
773   if (!gh_symbol_p (sym))
774     {
775       error ("Not a symbol");
776       ly_display_scm (sym);
777       return SCM_UNSPECIFIED;
778     }
779
780   if (sc)
781     {
782       sc->set_elt_property (sym, val);
783     }
784   else
785     {
786       error ("Not a score element");
787       ly_display_scm (elt);
788     }
789
790   return SCM_UNSPECIFIED;
791 }
792
793
794 SCM
795 ly_get_elt_property (SCM elt, SCM sym)
796 {
797   Score_element * sc = unsmob_element (elt);
798   
799   if (sc)
800     {
801       return sc->get_elt_property (sym);
802     }
803   else
804     {
805       error ("Not a score element");
806       ly_display_scm (elt);
807     }
808   return SCM_UNSPECIFIED;
809 }
810
811
812 void
813 Score_element::discretionary_processing()
814 {
815 }
816
817
818
819 SCM
820 spanner_get_bound (SCM slur, SCM dir)
821 {
822   return dynamic_cast<Spanner*> (unsmob_element (slur))->get_bound (to_dir (dir))->self_scm ();
823 }
824
825
826
827 static SCM interfaces_sym;
828
829 static void
830 init_functions ()
831 {
832   interfaces_sym = scm_permanent_object (ly_symbol2scm ("interfaces"));
833   
834   scm_make_gsubr ("ly-get-elt-property", 2, 0, 0, (SCM(*)(...))ly_get_elt_property);
835   scm_make_gsubr ("ly-set-elt-property", 3, 0, 0, (SCM(*)(...))ly_set_elt_property);
836   scm_make_gsubr ("ly-get-spanner-bound", 2 , 0, 0, (SCM(*)(...)) spanner_get_bound);
837 }
838
839 bool
840 Score_element::has_interface (SCM k)
841 {
842   //  if (mutable_property_alist_ == SCM_EOL)
843   //  return false;
844   
845   SCM ifs = get_elt_property (interfaces_sym);
846
847   return scm_memq (k, ifs) != SCM_BOOL_F;
848 }
849
850 void
851 Score_element::set_interface (SCM k)
852 {
853   if (has_interface (k))
854     return ;
855   else
856     {
857       set_elt_property (interfaces_sym,
858                         gh_cons  (k, get_elt_property (interfaces_sym)));
859     }
860 }
861
862
863 ADD_SCM_INIT_FUNC(scoreelt, init_functions);
864