]> git.donarmstrong.com Git - lilypond.git/blob - lily/mensural-ligature-engraver.cc
f40ce004b056b6bbc2df480a3b5bf20d5f9f213d
[lilypond.git] / lily / mensural-ligature-engraver.cc
1 /*
2   mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2002--2006 Juergen Reuter <reuter@ipd.uka.de>,
7   Pal Benko <benkop@freestart.hu>
8 */
9
10 #include "coherent-ligature-engraver.hh"
11 #include "font-interface.hh"
12 #include "international.hh"
13 #include "mensural-ligature.hh"
14 #include "note-column.hh"
15 #include "note-head.hh"
16 #include "output-def.hh"
17 #include "paper-column.hh"
18 #include "pitch.hh"
19 #include "rhythmic-head.hh"
20 #include "spanner.hh"
21 #include "staff-symbol-referencer.hh"
22 #include "stream-event.hh"
23 #include "warn.hh"
24
25 #include "translator.icc"
26
27 /*
28  * TODO: accidentals are aligned with the first note;
29  * they must appear ahead.
30  *
31  * TODO: prohibit ligatures having notes differing only in accidentals
32  * (like \[ a\breve g as \])
33  *
34  * TODO: dotted heads: avoid next note colliding with the dot, e.g. by
35  * putting it *above* (rather than after) the affected ligature head.
36  *
37  * TODO: do something with multiple voices within a ligature.  See
38  * for example:
39  * Ockeghem: Missa Ecce ancilla domini, bassus part, end of Christe.
40  *
41  * TODO: enhance robustness: in case of an illegal ligature (e.g. the
42  * input specifies a ligature that contains a minima), automatically
43  * break the ligature into smaller, valid pieces.  Such a piece may be
44  * a single note.
45  */
46
47 class Mensural_ligature_engraver : public Coherent_ligature_engraver
48 {
49
50 protected:
51   virtual Spanner *create_ligature_spanner ();
52   virtual void build_ligature (Spanner *ligature, vector<Grob_info> primitives);
53   DECLARE_TRANSLATOR_LISTENER (ligature);
54   
55 public:
56   TRANSLATOR_DECLARATIONS (Mensural_ligature_engraver);
57
58 private:
59   void transform_heads (vector<Grob_info> primitives);
60   void propagate_properties (Spanner *ligature, vector<Grob_info> primitives);
61   void fold_up_primitives (vector<Grob_info> primitives);
62 };
63
64 IMPLEMENT_TRANSLATOR_LISTENER (Mensural_ligature_engraver, ligature);
65 void
66 Mensural_ligature_engraver::listen_ligature (Stream_event *ev)
67 {
68   Ligature_engraver::listen_ligature (ev);
69 }
70
71 Mensural_ligature_engraver::Mensural_ligature_engraver ()
72 {
73   brew_ligature_primitive_proc = 
74     Mensural_ligature::brew_ligature_primitive_proc;
75 }
76
77 Spanner *
78 Mensural_ligature_engraver::create_ligature_spanner ()
79 {
80   return make_spanner ("MensuralLigature", SCM_EOL);
81 }
82
83 void
84 Mensural_ligature_engraver::transform_heads (vector<Grob_info> primitives)
85 {
86   if (primitives.size () < 2)
87     {
88       warning (_f ("ligature with less than 2 heads -> skipping"));
89       return;
90     }
91   int prev_pitch = 0;
92   bool at_beginning = true;
93
94   // needed so that we can check whether
95   // the previous note can be turned into a flexa
96   bool prev_brevis_shape = false;
97
98   bool prev_semibrevis = false;
99   Item *prev_primitive = NULL;
100
101   for (vsize i = 0, s = primitives.size (); i < s; i++)
102     {
103       Grob_info info = primitives[i];
104       Item *primitive = dynamic_cast<Item *> (info.grob ());
105       int duration_log = Note_head::get_balltype (primitive);
106
107       Stream_event *nr = info.event_cause ();
108
109       /*
110         ugh. why not simply check for pitch?
111       */
112       if (!nr->in_event_class ("note-event"))
113         {
114           nr->origin ()->warning
115             (_f ("cannot determine pitch of ligature primitive -> skipping"));
116           at_beginning = true;
117           continue;
118         }
119
120       int pitch = unsmob_pitch (nr->get_property ("pitch"))->steps ();
121       int delta_pitch = 0;
122
123       if (at_beginning)
124         {
125           if (i == s - 1)
126             {
127               // we can get here after invalid input
128               nr->origin ()->warning
129                 (_f ("single note ligature - skipping"));
130               break;
131             }
132           prev_semibrevis = prev_brevis_shape = false;
133           prev_primitive = NULL;
134         }
135       else
136         {
137           delta_pitch = pitch - prev_pitch;
138           if (delta_pitch == 0)
139             {
140               nr->origin ()->warning
141                 (_f ("prime interval within ligature -> skipping"));
142               at_beginning = true;
143               primitive->set_property ("primitive",
144                                        scm_from_int (MLP_NONE));
145               continue;
146             }
147         }
148
149       if (duration_log < -3 // is this possible at all???
150           || duration_log > 0)
151         {
152           nr->origin ()->warning
153             (_f ("mensural ligature: duration none of Mx, L, B, S -> skipping"));
154           primitive->set_property ("primitive",
155                                    scm_from_int (MLP_NONE));
156           at_beginning = true;
157           continue;
158         }
159
160       // apply_transition replacement begins
161       bool general_case = true;
162
163       // first check special cases
164       // 1. beginning
165       if (at_beginning)
166         {
167           // a. semibreves
168           if (duration_log == 0)
169             {
170               primitive->set_property ("primitive",
171                                        scm_from_int (MLP_UP | MLP_BREVIS));
172               prev_semibrevis = prev_brevis_shape = true;
173               general_case = false;
174             }
175           // b. descendens longa or brevis
176           else if (i < s - 1
177                    && (unsmob_pitch (primitives[i + 1].event_cause ()
178                                      ->get_property ("pitch"))->steps () < pitch)
179                    && duration_log > -3)
180             {
181               int left_stem = duration_log == -1 ? MLP_DOWN : 0;
182
183               primitive->set_property ("primitive",
184                                        scm_from_int (left_stem | MLP_BREVIS));
185               prev_brevis_shape = true;
186               prev_semibrevis = general_case = false;
187             }
188         }
189       // 2. initial semibrevis must be followed by another one
190       else if (prev_semibrevis)
191         {
192           prev_semibrevis = false;
193           if (duration_log == 0)
194             {
195               primitive->set_property ("primitive", scm_from_int (MLP_BREVIS));
196               general_case = false;
197             }
198           else
199             {
200               nr->origin ()->warning
201                 (_f ("semibrevis must be followed by another one -> skipping"));
202               primitive->set_property ("primitive",
203                                        scm_from_int (MLP_NONE));
204               at_beginning = true;
205               continue;
206             }
207         }
208       // 3. semibreves are otherwise not allowed
209       else if (duration_log == 0)
210         {
211           nr->origin ()->warning
212             (_f ("semibreves can only appear at the beginning of a ligature,\n"
213                  "and there may be only zero or two of them"));
214           primitive->set_property ("primitive",
215                                    scm_from_int (MLP_NONE));
216           at_beginning = true;
217           continue;
218         }
219       // 4. end, descendens
220       else if (i == s - 1 && delta_pitch < 0)
221         {
222           // brevis; previous note must be turned into flexa
223           if (duration_log == -1)
224             {
225               if (prev_brevis_shape)
226                 {
227                   prev_primitive->set_property
228                     ("primitive",
229                      scm_from_int
230                      (MLP_FLEXA
231                       | (scm_to_int (prev_primitive->get_property ("primitive"))
232                          & MLP_DOWN)));
233                   primitive->set_property ("primitive", scm_from_int (MLP_NONE));
234                   break; // no more notes, no join
235                 }
236               else
237                 {
238                   nr->origin ()->warning
239                     (_f ("invalid ligatura ending:\n"
240                          "when the last note is a descending brevis,\n"
241                          "the penultimate note must be another one,\n"
242                          "or the ligatura must be LB or SSB"));
243                   primitive->set_property ("primitive", scm_from_int (MLP_NONE));
244                   break;
245                 }
246             }
247           // longa
248           else if (duration_log == -2)
249             {
250               primitive->set_property ("primitive", scm_from_int (MLP_BREVIS));
251               general_case = false;
252             }
253           // else maxima; fall through regular case below
254         }
255
256       if (general_case)
257         {
258           static int const shape[3] = {MLP_MAXIMA, MLP_LONGA, MLP_BREVIS};
259
260           primitive->set_property ("primitive",
261                                    scm_from_int (shape[duration_log + 3]));
262           prev_brevis_shape = duration_log == -1;
263         }
264
265       // join_primitives replacement
266       if (!at_beginning)
267         {
268           /*
269             if the previous note is longa-shaped and this note is lower,
270             then the joining line may hide the stem, so it is made longer
271             to serve as stem as well
272           */
273           if (delta_pitch < 0
274               && (scm_to_int (prev_primitive->get_property ("primitive"))
275                   & MLP_LONGA))
276             {
277               delta_pitch -= 6;
278               // instead of number 6
279               // the legth of the longa stem should be queried something like
280               // Font_interface::get_default_font (ligature)->find_by_name
281               //  ("noteheads.s-2mensural").extent (Y_AXIS).length ()
282             }
283           prev_primitive->set_property ("join-right-amount",
284                                         scm_from_int (delta_pitch));
285           // perhaps set add-join as well
286         }
287       at_beginning = false;
288       prev_primitive = primitive;
289       prev_pitch = pitch;
290       // apply_transition replacement ends
291     }
292 }
293
294 /*
295  * A MensuralLigature grob consists of a bunch of NoteHead grobs that
296  * are glued together.  It (a) does not make sense to change
297  * properties like thickness or flexa-width from one head to the next
298  * within a ligature (this would totally screw up alignment), and (b)
299  * some of these properties (like flexa-width) are specific to
300  * e.g. the MensuralLigature (as in contrast to e.g. LigatureBracket),
301  * and therefore should not be handled in the NoteHead code (which is
302  * also used by LigatureBracket).  Therefore, we let the user control
303  * these properties via the concrete Ligature grob (like
304  * MensuralLigature) and then copy these properties as necessary to
305  * each of the NoteHead grobs.  This is what
306  * propagate_properties () does.
307  */
308 void
309 Mensural_ligature_engraver::propagate_properties (Spanner *ligature,
310                                                   vector<Grob_info> primitives)
311 {
312   Real thickness
313     = robust_scm2double (ligature->get_property ("thickness"), 1.4);
314   thickness
315     *= ligature->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
316
317   Real head_width
318     = Font_interface::get_default_font (ligature)->
319     find_by_name ("noteheads.s-1mensural").extent (X_AXIS).length ();
320   Real flexa_width
321     = robust_scm2double (ligature->get_property ("flexa-width"), 2);
322   Real maxima_head_width
323     = Font_interface::get_default_font (ligature)->
324     find_by_name ("noteheads.s-1neomensural").extent (X_AXIS).length ();
325
326   flexa_width *= Staff_symbol_referencer::staff_space (ligature);
327
328   Real half_flexa_width = 0.5 * (flexa_width + thickness);
329
330   for (vsize i = 0; i < primitives.size (); i++)
331     {
332       Item *primitive = dynamic_cast<Item *> (primitives[i].grob ());
333       int output = scm_to_int (primitive->get_property ("primitive"));
334       primitive->set_property ("thickness",
335                                scm_from_double (thickness));
336
337       switch (output & MLP_ANY)
338         {
339         case MLP_NONE:
340           primitive->set_property ("head-width",
341                                    scm_from_double (half_flexa_width));
342           break;
343         case MLP_BREVIS:
344         case MLP_LONGA:
345           primitive->set_property ("head-width",
346                                    scm_from_double (head_width));
347           break;
348         case MLP_MAXIMA:
349           primitive->set_property ("head-width",
350                                    scm_from_double (maxima_head_width));
351           break;
352         case MLP_FLEXA:
353           primitive->set_property ("head-width",
354                                    scm_from_double (half_flexa_width));
355           primitive->set_property ("flexa-width",
356                                    scm_from_double (flexa_width));
357           break;
358         default:
359           programming_error (_f ("unexpected case fall-through"));
360           break;
361         }
362     }
363 }
364
365 void
366 Mensural_ligature_engraver::fold_up_primitives (vector<Grob_info> primitives)
367 {
368   Item *first = 0;
369   Real distance = 0;
370   for (vsize i = 0; i < primitives.size (); i++)
371     {
372       Item *current = dynamic_cast<Item *> (primitives[i].grob ());
373       if (i == 0)
374         first = current;
375
376       get_set_column (current, first->get_column ());
377
378       if (i > 0)
379         current->translate_axis (distance, X_AXIS);
380
381       distance
382         += scm_to_double (current->get_property ("head-width"))
383         - scm_to_double (current->get_property ("thickness"));
384     }
385 }
386
387 void
388 Mensural_ligature_engraver::build_ligature (Spanner *ligature,
389                                             vector<Grob_info> primitives)
390 {
391   transform_heads (primitives);
392   propagate_properties (ligature, primitives);
393   fold_up_primitives (primitives);
394 }
395
396 ADD_ACKNOWLEDGER (Mensural_ligature_engraver, rest);
397 ADD_ACKNOWLEDGER (Mensural_ligature_engraver, note_head);
398 ADD_TRANSLATOR (Mensural_ligature_engraver,
399                 /* doc */ "Handles Mensural_ligature_events by glueing special ligature heads together.",
400                 /* create */ "MensuralLigature",
401                 /* accept */ "ligature-event",
402                 /* read */ "",
403                 /* write */ "");