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