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