]> git.donarmstrong.com Git - lilypond.git/blob - lily/ligature-engraver.cc
9922ca1c4819706cb478cf74aa55bcdb5bd2115f
[lilypond.git] / lily / ligature-engraver.cc
1 /*
2   ligature-engraver.cc -- implement Ligature_engraver
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2002--2006 Juergen Reuter <reuter@ipd.uka.de>
7 */
8
9 #include "ligature-engraver.hh"
10
11 #include "context.hh"
12 #include "international.hh"
13 #include "note-head.hh"
14 #include "rest.hh"
15 #include "spanner.hh"
16 #include "stream-event.hh"
17 #include "warn.hh"
18
19 #include "translator.icc"
20
21 /*
22  * This abstract class provides the general framework for ligatures of
23  * any kind.  It cares for handling start/stop ligatures events and
24  * collecting all noteheads inbetween, but delegates creation of a
25  * ligature spanner for each start/stop pair and typesetting of the
26  * ligature spanner to a concrete subclass.
27  *
28  * A concrete ligature engraver must subclass this class and provide
29  * functions create_ligature_spanner () and typeset_ligature
30  * (Spanner *, vector<Grob_info>).  Subclasses of this class basically
31  * fall into two categories.
32  *
33  * The first category consists of engravers that engrave ligatures in
34  * a way that really deserves the name ligature.  That is, they
35  * produce a single connected graphical object of fixed width,
36  * consisting of noteheads and other primitives.  Space may be
37  * inserted only after each ligature, if necessary, but in no case
38  * between the primitives of the ligature.  Accidentals have to be put
39  * to the left of the ligature, and not to the left of individual
40  * noteheads.  Class Coherent_ligature_engraver is the common
41  * superclass for all of these engravers.
42  *
43  * The second category is for engravers that are relaxed in the sense
44  * that they do not require to produce a single connected graphical
45  * object.  For example, in contemporary editions, ligatures are often
46  * marked, but otherwise use contemporary notation and spacing.  In
47  * this category, there is currently only a single class,
48  * Ligature_bracket_engraver, which marks each ligature with a
49  * horizontal sqare bracket, but otherwise leaves the appearance
50  * untouched.
51  */
52
53 /*
54  * TODO: lyrics/melisma/syllables: there should be at most one
55  * syllable of lyrics per ligature (i.e. for the lyrics context, a
56  * ligature should count as a single note, regardless of how many
57  * heads the ligature consists of).
58  *
59  * TODO: currently, you have to add/remove the proper
60  * Ligature_engraver (Ligature_bracket_engraver,
61  * Mensural_ligature_engraver) to the proper translator
62  * (e.g. VoiceContext) to choose between various representations.
63  * Since adding/removing an engraver to a translator is a global
64  * action in the layout block, you cannot mix various representations
65  * _within_ the same score.  Hence, for selecting a representation,
66  * one would rather like to have a property that can be set e.g. for
67  * several staves individually.  However, it seems that this approach
68  * would require to have a single, complicated Ligature_engraver that
69  * consists of all the code...  This needs further thoughts.
70  */
71 Ligature_engraver::Ligature_engraver ()
72 {
73   ligature_ = 0;
74   finished_ligature_ = 0;
75   events_drul_[LEFT] = events_drul_[RIGHT] = 0;
76   prev_start_event_ = 0;
77   last_bound_ = 0;
78   brew_ligature_primitive_proc = SCM_EOL;
79 }
80
81 void
82 Ligature_engraver::listen_ligature (Stream_event *ev)
83 {
84   Direction d = to_dir (ev->get_property ("span-direction"));
85   ASSIGN_EVENT_ONCE (events_drul_[d], ev);
86 }
87
88 void
89 Ligature_engraver::process_music ()
90 {
91   if (events_drul_[STOP])
92     {
93       if (!ligature_)
94         {
95           events_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
96           return;
97         }
98
99       if (!last_bound_)
100         events_drul_[STOP]->origin ()->warning (_ ("no right bound"));
101       else
102         ligature_->set_bound (RIGHT, last_bound_);
103
104       prev_start_event_ = 0;
105       finished_primitives_ = primitives_;
106       finished_ligature_ = ligature_;
107       primitives_.clear ();
108       ligature_ = 0;
109     }
110   last_bound_ = unsmob_grob (get_property ("currentMusicalColumn"));
111
112   if (ligature_)
113     {
114       // TODO: maybe forbid breaks only if not transcribing
115       context ()->get_score_context ()->set_property ("forbidBreak", SCM_BOOL_T);
116     }
117
118   if (events_drul_[START])
119     {
120       if (ligature_)
121         {
122           events_drul_[START]->origin ()->warning (_ ("already have a ligature"));
123           return;
124         }
125
126       prev_start_event_ = events_drul_[START];
127       ligature_ = create_ligature_spanner ();
128
129       Grob *bound = unsmob_grob (get_property ("currentMusicalColumn"));
130       if (!bound)
131         events_drul_[START]->origin ()->warning (_ ("no left bound"));
132       else
133         ligature_->set_bound (LEFT, bound);
134
135       ligature_start_mom_ = now_mom ();
136
137       // TODO: dump cause into make_item/spanner. 
138       // announce_grob (ligature_, events_drul_[START]->self_scm ());
139     }
140 }
141
142 void
143 Ligature_engraver::stop_translation_timestep ()
144 {
145   if (finished_ligature_)
146     {
147       if (!finished_primitives_.size ())
148         {
149           finished_ligature_->programming_error ("Ligature_engraver::stop_translation_timestep (): "
150                                                  "junking empty ligature");
151         }
152       else
153         {
154           typeset_ligature (finished_ligature_, finished_primitives_);
155           finished_primitives_.clear ();
156         }
157       finished_ligature_ = 0;
158     }
159
160   events_drul_[START] = 0;
161   events_drul_[STOP] = 0;
162 }
163
164 void
165 Ligature_engraver::finalize ()
166 {
167   if (finished_ligature_)
168     {
169       typeset_ligature (finished_ligature_, finished_primitives_);
170       finished_primitives_.clear ();
171       finished_ligature_ = 0;
172     }
173   if (ligature_)
174     {
175       prev_start_event_->origin ()->warning (_ ("unterminated ligature"));
176       ligature_->suicide ();
177     }
178 }
179
180 Spanner *
181 Ligature_engraver::current_ligature ()
182 {
183   return ligature_;
184 }
185
186 void
187 Ligature_engraver::acknowledge_note_head (Grob_info info)
188 {
189   if (ligature_)
190     {
191       primitives_.push_back (info);
192       if (info.grob () && brew_ligature_primitive_proc != SCM_EOL)
193         {
194           info.grob ()->set_property ("stencil", brew_ligature_primitive_proc);
195         }
196     }
197 }
198
199 void
200 Ligature_engraver::acknowledge_rest (Grob_info info)
201 {
202   if (ligature_)
203     {
204       info.event_cause ()->origin ()->warning (_ ("ignoring rest: ligature may not contain rest"));
205       prev_start_event_->origin ()->warning (_ ("ligature was started here"));
206       // TODO: maybe better should stop ligature here rather than
207       // ignoring the rest?
208     }
209 }
210
211 // no ADD_ACKNOWLEDGER / ADD_ACKNOWLEDGER / ADD_TRANSLATOR macro calls
212 // since this class is abstract