]> git.donarmstrong.com Git - lilypond.git/blob - lily/vaticana-ligature-engraver.cc
* Documentation/user/refman.itely (Gregorian square neumes
[lilypond.git] / lily / vaticana-ligature-engraver.cc
1 /*
2   vaticana-ligature-engraver.cc -- implement Vaticana_ligature_engraver
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c)  2003 Juergen Reuter <reuter@ipd.uka.de>
7  */
8
9 #include "gregorian-ligature-engraver.hh"
10 #include "gregorian-ligature.hh"
11 #include "vaticana-ligature.hh"
12 #include "item.hh"
13 #include "spanner.hh"
14 #include "staff-symbol-referencer.hh"
15 #include "font-interface.hh"
16 #include "warn.hh"
17 #include "paper-def.hh"
18 #include "paper-column.hh"
19
20 /*
21  * This class implements the notation specific aspects of Vaticana
22  * style ligatures for Gregorian chant notation.
23  */
24 class Vaticana_ligature_engraver : public Gregorian_ligature_engraver
25 {
26
27 private:
28   bool is_stacked_head (int prefix_set,
29                         int context_info);
30   Real align_heads (Array<Grob_info> primitives,
31                     Real flexa_width,
32                     Real join_thickness);
33
34 public:
35   TRANSLATOR_DECLARATIONS(Vaticana_ligature_engraver);
36
37 protected:
38   virtual Spanner *create_ligature_spanner ();
39   virtual void transform_heads (Spanner *ligature,
40                                 Array<Grob_info> primitives);
41 };
42
43 Vaticana_ligature_engraver::Vaticana_ligature_engraver ()
44 {
45 }
46
47 Spanner *
48 Vaticana_ligature_engraver::create_ligature_spanner ()
49 {
50   return new Spanner (get_property ("VaticanaLigature"));
51 }
52
53 bool
54 Vaticana_ligature_engraver::is_stacked_head (int prefix_set,
55                                              int context_info)
56 {
57       bool is_stacked_b;
58
59       // upper head of pes is stacked upon lower head of pes ...
60       is_stacked_b = context_info & PES_UPPER;
61
62       // ... unless this note starts a flexa
63       if (context_info & FLEXA_LEFT)
64         is_stacked_b = false;
65
66       // ... or another pes
67       if (context_info & PES_LOWER)
68         is_stacked_b = false;
69
70       // ... or the previous note is a semivocalis or inclinatum
71       if (context_info & AFTER_DEMINUTUM)
72         is_stacked_b = false;
73
74       // auctum head is never stacked upon preceding note
75       if (prefix_set & AUCTUM)
76         is_stacked_b = false;
77
78       // virga is never stacked upon preceding note
79       if (prefix_set & VIRGA)
80         is_stacked_b = false;
81
82       // oriscus is never stacked upon preceding note
83       if (prefix_set & ORISCUS)
84         is_stacked_b = false;
85
86       if ((prefix_set & DEMINUTUM) &&
87           !(prefix_set & INCLINATUM) &&
88           (context_info & FLEXA_RIGHT))
89         is_stacked_b = true; // semivocalis head of deminutus form
90
91       return is_stacked_b;
92 }
93
94 Real
95 Vaticana_ligature_engraver::align_heads (Array<Grob_info> primitives,
96                                          Real flexa_width,
97                                          Real join_thickness)
98 {
99   Item *first_primitive = dynamic_cast<Item*> (primitives[0].grob_);
100   Real ligature_width = 0.0;
101
102   /*
103    * Amount of extra space two put between some particular
104    * configurations of adjacent heads.
105    *
106    * TODO: make this a property of primtive grobs.
107    */
108   Real extra_space = 2.0 * join_thickness;
109
110   for (int i = 0; i < primitives.size(); i++)
111     {
112       Item *primitive = dynamic_cast<Item*> (primitives[i].grob_);
113
114       /*
115        * Get glyph_name, delta_pitch and context_info for this head.
116        */
117
118       SCM glyph_name_scm = primitive->get_grob_property ("glyph-name");
119       if (glyph_name_scm == SCM_EOL)
120         {
121           primitive->programming_error ("Vaticana_ligature:"
122                                         "undefined glyph-name -> "
123                                         "ignoring grob");
124           return 0.0;
125         }
126       String glyph_name = ly_scm2string (glyph_name_scm);
127
128       int delta_pitch;
129       SCM delta_pitch_scm = primitive->get_grob_property ("delta-pitch");
130       if (delta_pitch_scm != SCM_EOL)
131         {
132           delta_pitch = gh_scm2int (delta_pitch_scm);
133         }
134       else
135         {
136           primitive->programming_error ("Vaticana_ligature:"
137                                         "delta-pitch undefined -> "
138                                         "ignoring grob");
139           return 0.0;
140         }
141
142       int context_info;
143       SCM context_info_scm = primitive->get_grob_property ("context-info");
144       if (context_info_scm != SCM_EOL)
145         {
146           context_info = gh_scm2int (context_info_scm);
147         }
148       else
149         {
150           primitive->programming_error ("Vaticana_ligature:"
151                                         "context-info undefined -> "
152                                         "ignoring grob");
153           return 0.0;
154         }
155
156       /*
157        * Now determine width and x-offset of head.
158        */
159
160       Real head_width;
161       Real x_offset;
162
163       if (context_info & STACKED_HEAD)
164         {
165           /*
166            * This head is stacked upon the previous one; hence, it
167            * does not contribute to the total width of the ligature,
168            * and its width is assumed to be 0.0.  Moreover, it is
169            * shifted to the left by its width such that the right side
170            * of this and the other head are horizontally aligned.
171            */
172           head_width = 0.0;
173           x_offset = join_thickness -
174             Font_interface::get_default_font (primitive)->
175             find_by_name ("noteheads-" + glyph_name).extent (X_AXIS).length ();
176         }
177       else if (!String::compare (glyph_name, "flexa") ||
178                !String::compare (glyph_name, ""))
179         {
180           /*
181            * This head represents either half of a flexa shape.
182            * Hence, it is assigned half the width of this shape.
183            */
184           head_width = 0.5 * flexa_width;
185           x_offset = 0.0;
186         }
187       else // retrieve width from corresponding font
188         {
189           head_width =
190             Font_interface::get_default_font (primitive)->
191             find_by_name ("noteheads-" + glyph_name).extent (X_AXIS).length ();
192           x_offset = 0.0;
193         }
194
195       /*
196        * Save the head's final x-offset.
197        */
198       primitive->set_grob_property ("x-offset",
199                                     gh_double2scm (x_offset));
200
201       /*
202        * If the head is the 2nd head of a pes or flexa (but not a
203        * flexa shape), mark this head to be joined with the left-side
204        * neighbour head (i.e. the previous head) by a vertical beam.
205        */
206       if ((context_info & PES_UPPER) ||
207           ((context_info & FLEXA_RIGHT) &&
208            !(context_info & PES_LOWER)))
209         {
210           primitive->set_grob_property ("join-left", gh_bool2scm(true));
211
212           /*
213            * Create a small overlap of adjacent heads so that the join
214            * can be drawn perfectly between them.
215            */
216           ligature_width -= join_thickness;
217         }
218       else if (!String::compare (glyph_name, ""))
219         {
220           /*
221            * This is the 2nd (virtual) head of flexa shape.  Join it
222            * tightly with 1st head, i.e. do *not* add additional
223            * space, such that next head will not be off from the flexa
224            * shape.
225            */
226         }
227       else if (context_info & AFTER_VIRGA)
228         {
229           /*
230            * After a virga, make a an additional small space such that
231            * the appendix on the right side of the head does not touch
232            * the following head.
233            */
234           ligature_width += extra_space;
235         }
236       else if (delta_pitch == 0)
237         {
238           /*
239            * If there are two adjacent noteheads with the same pitch,
240            * add additional small space between them, such that they
241            * do not touch each other.
242            */
243           ligature_width += extra_space;
244         }
245
246       /*
247        * Horizontally line-up this head to form a ligature.
248        */
249       get_set_column (primitive, first_primitive->get_column ());
250       primitive->translate_axis (ligature_width, X_AXIS);
251       ligature_width += head_width;
252
253     }
254
255   /*
256    * Add extra horizontal padding space after ligature, such that
257    * neighbouring ligatures do not touch each other.
258    */
259   ligature_width += extra_space;
260
261   return ligature_width;
262 }
263
264 void
265 Vaticana_ligature_engraver::transform_heads (Spanner *ligature,
266                                              Array<Grob_info> primitives)
267 {
268   Real flexa_width;
269   SCM flexa_width_scm = ligature->get_grob_property ("flexa-width");
270   if (flexa_width_scm != SCM_EOL)
271     {
272       flexa_width = gh_scm2double (flexa_width_scm);
273     }
274   else
275     {
276       ligature->programming_error ("Vaticana_ligature_engraver:"
277                                    "flexa-width undefined; "
278                                    "assuming 2.0 staff space");
279       flexa_width =
280         2.0 * Staff_symbol_referencer::staff_space (ligature);
281     }
282
283   Real join_thickness;
284   SCM join_thickness_scm = ligature->get_grob_property ("thickness");
285   if (join_thickness_scm != SCM_EOL)
286     {
287       join_thickness = gh_scm2double (join_thickness_scm);
288     }
289   else
290     {
291       ligature->programming_error ("Vaticana_ligature_engraver:"
292                                    "thickness undefined; "
293                                    "assuming 1.4 linethickness");
294       join_thickness = 1.4;
295     }
296   join_thickness *= ligature->get_paper ()->get_var ("linethickness");
297
298   Item *prev_primitive = 0;
299   int prev_prefix_set = 0;
300   int prev_context_info = 0;
301   int prev_pitch = 0;
302   String prev_glyph_name = "";
303   for (int i = 0; i < primitives.size(); i++) {
304     Item *primitive = dynamic_cast<Item*> (primitives[i].grob_);
305     Music *music_cause = primitives[i].music_cause ();
306
307     /* compute interval between previous and current primitive */
308     int pitch =
309       unsmob_pitch (music_cause->get_mus_property ("pitch"))->steps ();
310     int delta_pitch;
311     if (i == 0)
312       {
313         delta_pitch = 0;
314       }
315     else
316       {
317         delta_pitch = pitch - prev_pitch;
318       }
319
320     /* retrieve & complete prefix_set and context_info */
321     int prefix_set =
322       gh_scm2int (primitive->get_grob_property ("prefix-set"));
323     int context_info =
324       gh_scm2int (primitive->get_grob_property ("context-info"));
325     if (is_stacked_head (prefix_set, context_info))
326       {
327         context_info |= STACKED_HEAD;
328         primitive->set_grob_property ("context-info",
329                                       gh_int2scm (context_info));
330       }
331
332     /*
333      * Now determine which head to typeset (this is context sensitive
334      * information, since it depends on neighbouring heads; therefore,
335      * this decision must be made here in the engraver rather than in
336      * the backend).
337      */
338     String glyph_name;
339     if (prefix_set & VIRGA)
340       glyph_name = "vaticana_virga";
341     else if (prefix_set & QUILISMA)
342       glyph_name = "vaticana_quilisma";
343     else if (prefix_set & ORISCUS)
344       glyph_name = "solesmes_oriscus";
345     else if (prefix_set & STROPHA)
346       if (prefix_set & AUCTUM)
347         glyph_name = "solesmes_stropha_aucta";
348       else glyph_name = "solesmes_stropha";
349     else if (prefix_set & INCLINATUM)
350       if (prefix_set & AUCTUM)
351         glyph_name = "solesmes_incl_auctum";
352       else if (prefix_set & DEMINUTUM)
353         glyph_name = "solesmes_incl_parvum";
354       else
355         glyph_name = "vaticana_inclinatum";
356     else if (prefix_set & DEMINUTUM)
357       if (i == 0)
358         {
359           // initio debilis
360           glyph_name = "vaticana_reverse_plica";
361         }
362       else if (delta_pitch > 0)
363         {
364           // epiphonus
365           if (!(prev_context_info & FLEXA_RIGHT))
366             {
367               prev_glyph_name = "vaticana_epiphonus";
368             }
369           glyph_name = "vaticana_plica";
370         }
371       else // (delta_pitch <= 0)
372         {
373           // cephalicus
374           if (!(prev_context_info & FLEXA_RIGHT))
375             {
376               if (i > 1)
377                 {
378                   prev_glyph_name = "vaticana_inner_cephalicus";
379                 }
380               else
381                 {
382                   prev_glyph_name = "vaticana_cephalicus";
383                 }
384             }
385           glyph_name = "vaticana_reverse_plica";
386         }
387     else if (prefix_set & (CAVUM | LINEA))
388       if ((prefix_set & CAVUM) && (prefix_set & LINEA))
389         glyph_name = "vaticana_linea_punctum_cavum";
390       else if (prefix_set & CAVUM)
391         glyph_name = "vaticana_punctum_cavum";
392       else
393         glyph_name = "vaticana_linea_punctum";
394     else if (prefix_set & AUCTUM)
395       if (prefix_set & ASCENDENS)
396         glyph_name = "solesmes_auct_asc";
397       else
398         glyph_name = "solesmes_auct_desc";
399     else if ((prefix_set & PES_OR_FLEXA) &&
400              (context_info & PES_LOWER) &&
401              (context_info & FLEXA_RIGHT))
402       glyph_name = ""; // second head of flexa shape
403     else if ((context_info & STACKED_HEAD) &&
404              (context_info & PES_UPPER))
405       if (delta_pitch > 1)
406         glyph_name = "vaticana_upes";
407       else
408         glyph_name = "vaticana_vupes";
409     else if ((context_info & FLEXA_LEFT) &&
410              !(prefix_set && PES_OR_FLEXA))
411       glyph_name = "vaticana_rvirga";
412     else
413       glyph_name = "vaticana_punctum";
414
415     /*
416      * If the head for the current primitive represents the right head
417      * of a flexa or the upper head of a pes, then this may affect the
418      * shape of the previous head.
419      */
420     if (prefix_set & PES_OR_FLEXA)
421       {
422         if ((context_info & FLEXA_RIGHT) && (context_info & PES_LOWER))
423           {
424             // join the two flexa heads into a single curved flexa shape
425             prev_glyph_name = "flexa";
426             prev_primitive->set_grob_property ("flexa-height",
427                                                gh_int2scm (delta_pitch));
428             prev_primitive->set_grob_property ("flexa-width",
429                                                gh_double2scm (flexa_width));
430             bool add_stem = !(prev_prefix_set && PES_OR_FLEXA);
431             prev_primitive->set_grob_property ("add-stem",
432                                                gh_bool2scm (add_stem));
433           }
434         if ((context_info & PES_UPPER) && (context_info & STACKED_HEAD))
435           {
436             if (!String::compare (prev_glyph_name, "vaticana_punctum"))
437               prev_glyph_name = "vaticana_lpes";
438           }
439       }
440
441     if (prev_primitive)
442       prev_primitive->set_grob_property ("glyph-name",
443                                          scm_makfrom0str (prev_glyph_name.to_str0 ()));
444
445     primitive->set_grob_property ("delta-pitch",
446                                   gh_int2scm (delta_pitch));
447
448     /*
449      * In the backend, flexa shapes and joins need to know about
450      * thickness.  Hence, for simplicity, let's distribute the
451      * ligature grob's value for thickness to each ligature head (even
452      * if not all of them need to know).
453      */
454     primitive->set_grob_property ("thickness", gh_double2scm (join_thickness));
455
456     prev_primitive = primitive;
457     prev_prefix_set = prefix_set;
458     prev_context_info = context_info;
459     prev_pitch = pitch;
460     prev_glyph_name = glyph_name;
461   }
462
463   prev_primitive->set_grob_property ("glyph-name",
464                                      scm_makfrom0str (prev_glyph_name.to_str0 ()));
465
466 #if 0
467   Real ligature_width =
468 #endif
469
470   align_heads (primitives, flexa_width, join_thickness);
471
472 #if 0 // experimental code to collapse spacing after ligature
473   /* TODO: set to max(old/new spacing-increment), since other
474      voices/staves also may want to set this property. */
475   Item *first_primitive = dynamic_cast<Item*> (primitives[0].grob_);
476   Paper_column *paper_column = first_primitive->get_column();
477   paper_column->warning (_f ("Vaticana_ligature_engraver: "
478                              "setting `spacing-increment = %f': ptr=%ul",
479                              ligature_width, paper_column));
480   paper_column->
481     set_grob_property("forced-spacing", gh_double2scm (ligature_width));
482 #endif
483 }
484
485
486 ENTER_DESCRIPTION (Vaticana_ligature_engraver,
487 /* descr */       "Handles ligatures by glueing special ligature heads together.",
488 /* creats*/       "VaticanaLigature",
489 /* accepts */     "ligature-event abort-event",
490 /* acks  */      "note-head-interface rest-interface",
491 /* reads */       "",
492 /* write */       "");