]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie-engraver.cc
b725b64a4b010b9aa2a2f2d5cf3003027ea475a3
[lilypond.git] / lily / tie-engraver.cc
1 /*   
2   tie-engraver.cc --  implement Tie_engraver
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c) 1998--2001 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   
8  */
9
10 #include "command-request.hh"
11 #include "musical-request.hh"
12 #include "tie.hh"
13 #include "translator-group.hh"
14 #include "spanner.hh"
15 #include "tie-column.hh"
16 #include "engraver.hh"
17 #include "item.hh"
18 #include "grob-pitch-tuple.hh"
19 #include "note-head.hh"
20
21 /**
22    Manufacture ties.  Acknowledge noteheads, and put them into a
23    priority queue. If we have a Tie_req, connect the notes that finish
24    just at this time, and note that start at this time.
25
26    TODO: Remove the dependency on musical info. We should tie on the
27    basis of position and duration-log of the heads (not of the reqs).
28
29
30    TODO: figure this out: currently, this engravers ties note heads
31    that have the same Y-position (and does not look at pitch). This
32    means that we will fuck up with a clef-change. How should
33    clef-changes during ties be handled, or should they not?
34
35    
36 */
37 class Tie_engraver : public Engraver
38 {
39   Moment end_mom_;
40   Moment next_end_mom_;
41
42   Tie_req *req_l_;
43   Link_array<Grob> now_heads_;
44   Link_array<Grob> stopped_heads_;
45   Link_array<Grob> tie_p_arr_;
46
47   Spanner * tie_column_p_;
48   
49   void set_melisma (bool);
50   
51 protected:
52   virtual void start_translation_timestep ();
53   virtual void stop_translation_timestep ();
54   virtual void acknowledge_grob (Grob_info);
55   virtual bool try_music (Music*);
56   virtual void create_grobs ();
57   void typeset_tie (Grob*);
58 public:
59   TRANSLATOR_DECLARATIONS(Tie_engraver);
60 };
61
62
63
64 Tie_engraver::Tie_engraver ()
65 {
66   req_l_ = 0;
67   tie_column_p_ = 0;
68 }
69
70
71 bool
72 Tie_engraver::try_music (Music *m)
73 {
74   if (Tie_req * c = dynamic_cast<Tie_req*> (m))
75     {
76       /*      if (end_mom_ > now_mom ())
77        return false;
78       */
79       req_l_ = c;
80       SCM m = get_property ("automaticMelismata");
81       bool am = gh_boolean_p (m) &&gh_scm2bool (m);
82       if (am)
83         {
84           set_melisma (true);
85         }
86       return true;
87     }
88   return false;
89 }
90
91 void
92 Tie_engraver::set_melisma (bool m)
93 {
94   daddy_trans_l_->set_property ("tieMelismaBusy", m ? SCM_BOOL_T : SCM_BOOL_F);
95 }
96
97 void
98 Tie_engraver::acknowledge_grob (Grob_info i)
99 {
100   if (Note_head::has_interface (i.grob_l_))
101     {
102       now_heads_.push (i.grob_l_);
103     }
104 }
105
106 int
107 head_position_compare (Grob  *const&a,Grob  *const&b)
108 {
109   return sign (gh_scm2double (a->get_grob_property ("staff-position"))
110                - gh_scm2double (b->get_grob_property ("staff-position")));
111 }
112
113 void
114 Tie_engraver::create_grobs ()
115 {
116   if (req_l_)
117     {
118       now_heads_.sort (&head_position_compare);
119       stopped_heads_.sort (&head_position_compare);
120
121       SCM head_list = SCM_EOL;
122       
123       int j = stopped_heads_.size ()-1;
124       int i = now_heads_.size ()-1;
125
126       while (i >= 0 && j >=0)
127         {
128           int comp
129             = head_position_compare (now_heads_[i], stopped_heads_[j]);
130
131           if (comp)
132             {
133               (comp < 0) ? j -- : i--;
134               continue;
135             }
136           else
137             {
138               head_list  = gh_cons (gh_cons (stopped_heads_[j]->self_scm (),
139                                              now_heads_[i]->self_scm ()),
140                                     head_list);
141
142               now_heads_.del (i);
143               stopped_heads_.del (j);
144               i--;
145               j--;
146             }
147         }
148
149       SCM basic = get_property ("Tie");
150       SCM sparse = get_property ("sparseTies");
151       if (to_boolean (sparse))
152         {
153           int i = scm_ilength (head_list);
154
155           if (!i)
156             return;
157           
158           SCM pair = scm_list_ref (head_list, gh_int2scm (i/2));
159           
160           Spanner * p = new Spanner (basic);
161
162           Tie::set_interface (p);
163           Tie::set_head (p,LEFT, dynamic_cast<Item*> (unsmob_grob (ly_car (pair))));
164           Tie::set_head (p,RIGHT, dynamic_cast<Item*> (unsmob_grob (ly_cdr (pair))));
165           
166           tie_p_arr_.push (p);
167           announce_grob (p, req_l_);
168         }
169       else for (SCM s = head_list; gh_pair_p (s); s = ly_cdr (s))
170         {
171           Grob * p = new Spanner (basic);
172           Tie::set_interface (p);
173           
174           Tie::set_head (p, LEFT, dynamic_cast<Item*> (unsmob_grob (ly_caar (s))));
175           Tie::set_head (p, RIGHT, dynamic_cast<Item*> (unsmob_grob (ly_cdar (s))));
176           
177           tie_p_arr_.push (p);
178           announce_grob (p, req_l_);
179         }
180
181       if (tie_p_arr_.size () > 1 && !tie_column_p_)
182         {
183           tie_column_p_ = new Spanner (get_property ("TieColumn"));
184           Tie_column::set_interface (tie_column_p_);
185           for (int i = tie_p_arr_.size (); i--;)
186             Tie_column::add_tie (tie_column_p_,tie_p_arr_ [i]);
187           announce_grob (tie_column_p_, 0);
188         }
189     }
190 }
191
192
193 void
194 Tie_engraver::stop_translation_timestep ()
195 {
196   req_l_ = 0;
197
198   now_heads_.clear ();
199
200   /*
201     we don't warn for no ties, since this happens naturally when you
202     use skipTypesetting.  */
203   
204   for (int i=0; i<  tie_p_arr_.size (); i++)
205    {
206       typeset_tie (tie_p_arr_[i]);
207     }
208   tie_p_arr_.clear ();
209   if (tie_column_p_)
210     {
211       typeset_grob (tie_column_p_);
212       tie_column_p_ =0;
213     }
214 }
215
216 void
217 Tie_engraver::typeset_tie (Grob *her)
218 {
219   if (! (Tie::head (her,LEFT) && Tie::head (her,RIGHT)))
220     warning (_ ("lonely tie"));
221
222   Direction d = LEFT;
223   Drul_array<Grob *> new_head_drul;
224   new_head_drul[LEFT] = Tie::head (her,LEFT);
225   new_head_drul[RIGHT] = Tie::head (her,RIGHT);  
226   do {
227     if (!Tie::head (her,d))
228       new_head_drul[d] = Tie::head (her, (Direction)-d);
229   } while (flip (&d) != LEFT);
230
231   index_set_cell (her->get_grob_property ("heads"), LEFT, new_head_drul[LEFT]->self_scm ());
232   index_set_cell (her->get_grob_property ("heads"), RIGHT, new_head_drul[RIGHT]->self_scm ());
233
234   typeset_grob (her);
235 }
236
237 void
238 Tie_engraver::start_translation_timestep ()
239 {
240   SCM m = get_property ("automaticMelismata");
241   if (to_boolean (m))
242     {
243       set_melisma (false);
244     }
245
246   SCM grobs = get_property ("busyGrobs");
247   Moment now = now_mom();
248   stopped_heads_.clear ();
249   
250   for (; gh_pair_p (grobs); grobs = gh_cdr (grobs))
251     {
252       Grob * grob  = unsmob_grob (gh_cdar (grobs));
253       Moment end  =*unsmob_moment (gh_caar (grobs));
254       
255       /*
256         This is slightly ugh: we are now confunding the frontend
257         (iterators) and the backend (note heads) */
258       if (end > now)
259         break;
260       else if (end == now
261                && Note_head::has_interface (grob))
262         stopped_heads_.push (grob);
263     }
264 }
265
266
267 ENTER_DESCRIPTION(Tie_engraver,
268 /* descr */       "Generate ties between noteheads of equal pitch.",
269 /* creats*/       "Tie TieColumn",
270 /* acks  */       "rhythmic-head-interface",
271 /* reads */       "sparseTies tieMelismaBusy",
272 /* write */       "");