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