]> git.donarmstrong.com Git - lilypond.git/commitdiff
''
authorHan-Wen Nienhuys <hanwen@xs4all.nl>
Sun, 19 May 2002 00:06:27 +0000 (00:06 +0000)
committerHan-Wen Nienhuys <hanwen@xs4all.nl>
Sun, 19 May 2002 00:06:27 +0000 (00:06 +0000)
input/bugs/tremolo.ly [new file with mode: 0644]
input/test/mensural-ligatures.ly [new file with mode: 0644]
lily/function-documentation.cc [new file with mode: 0644]
lily/include/ligature-head.hh [new file with mode: 0644]
lily/include/mensural-ligature.hh [new file with mode: 0644]
lily/ligature-engraver.cc [new file with mode: 0644]
lily/ligature-head.cc [new file with mode: 0644]
lily/mensural-ligature-engraver.cc [new file with mode: 0644]
lily/mensural-ligature.cc [new file with mode: 0644]

diff --git a/input/bugs/tremolo.ly b/input/bugs/tremolo.ly
new file mode 100644 (file)
index 0000000..0b9b707
--- /dev/null
@@ -0,0 +1,12 @@
+
+
+%{
+
+Hi,
+
+In the following example, the tremolo beams are too 
+close to the long beam.
+
+%}
+
+\score{\notes\relative c'{ c8:32 d:32 e:32 f:32 }}
diff --git a/input/test/mensural-ligatures.ly b/input/test/mensural-ligatures.ly
new file mode 100644 (file)
index 0000000..44a6f2b
--- /dev/null
@@ -0,0 +1,78 @@
+\version "1.3.146"
+\header {
+    title      = "mensural ligature test"
+    date       = "2002"
+}
+
+\include "paper26.ly"
+
+% Note the horizontal alignment of the fermatas that obeys to the
+% graphical width of the ligatures rather to the musical moment in time.
+% This is intended behaviour.
+
+voice = \notes \transpose c'' {
+  \property Score.timing = ##f
+  \property Score.defaultBarType = "empty"
+  \property Voice.NoteHead \set #'font-family = #'ancient
+  \property Voice.NoteHead \override #'style = #'mensural
+  g\longa c\breve a\breve f\breve d'\longa^\fermata
+  \bar "|"
+  \[
+    g\longa c\breve a\breve f\breve d'\longa^\fermata
+  \]
+  \bar "|"
+  e1 f1 a\breve g\longa^\fermata
+  \bar "|"
+  \[
+    e1 f1 a\breve g\longa^\fermata
+  \]
+  \bar "|"
+  e1 f1 a\breve g\longa^\fermata
+  \bar "||"
+}
+
+upperStaff = \context Staff = upperStaff <
+  \context MensuralVoice <
+    \voice
+  >
+>
+
+lowerStaff = \context Staff = lowerStaff <
+  \context TranscribedVoice <
+    \voice
+  >
+>
+
+\score {
+    \context ChoirStaff <
+       \upperStaff
+       \lowerStaff
+    >
+    \paper {
+       stafflinethickness = \staffspace / 5.0
+       \translator {
+           \VoiceContext
+           \name MensuralVoice
+           \alias Voice
+           \remove Ligature_bracket_engraver
+           \consists Mensural_ligature_engraver
+       }
+       \translator {
+           \VoiceContext
+           \name TranscribedVoice
+           \alias Voice
+           \remove Mensural_ligature_engraver
+           \consists Ligature_bracket_engraver
+       }
+       \translator {
+           \StaffContext
+           \accepts MensuralVoice
+           \accepts TranscribedVoice
+        }
+       \translator {
+           \HaraKiriStaffContext
+           \accepts MensuralVoice
+           \accepts TranscribedVoice
+        }
+    }
+}
diff --git a/lily/function-documentation.cc b/lily/function-documentation.cc
new file mode 100644 (file)
index 0000000..571777b
--- /dev/null
@@ -0,0 +1,24 @@
+#include "lily-guile.hh"
+#include "protected-scm.hh"
+
+static Protected_scm doc_hash_table ;
+
+void ly_add_function_documentation (char const * fname,
+                                   char const * varlist,
+                                   char const * doc)
+{
+  if (!gh_vector_p (doc_hash_table ))
+    doc_hash_table = scm_make_vector (gh_int2scm (59), SCM_EOL);
+
+
+  SCM entry = gh_cons (ly_str02scm (varlist), ly_str02scm (doc));
+  scm_hashq_set_x (doc_hash_table, ly_symbol2scm (fname), entry);
+}
+
+
+LY_DEFINE(ly_get_all_function_documentation, "ly-get-all-function-documentation",
+         0,0,0, (),
+         "Get a hash table with all lilypond Scheme extension functions.")
+{
+  return doc_hash_table;
+}
diff --git a/lily/include/ligature-head.hh b/lily/include/ligature-head.hh
new file mode 100644 (file)
index 0000000..c502c04
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+  ligature-head.hh -- part of GNU LilyPond
+
+  (c) 2002 Juergen Reuter <reuter@ipd.uka.de>
+*/
+
+#ifndef LIGATURE_HEAD_HH
+#define LIGATURE_HEAD_HH
+
+#include "lily-guile.hh"
+#include "molecule.hh"
+
+/** ball within a ligature.  Also takes care of ledger lines.
+
+    LigatureHead is a kind of RhythmicHead, see there.
+
+  Read-only:
+*/
+
+class Ligature_head 
+{
+public:
+  DECLARE_SCHEME_CALLBACK (brew_molecule, (SCM ));
+  static bool has_interface (Grob*);
+  
+};
+#endif // LIGATURE_HEAD_HH
+
diff --git a/lily/include/mensural-ligature.hh b/lily/include/mensural-ligature.hh
new file mode 100644 (file)
index 0000000..3c8ed08
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+  mensural-ligature.hh -- part of GNU LilyPond
+
+  source file of the GNU LilyPond music typesetter
+  
+  (c) 2002 Juergen Reuter <reuter@ipd.uka.de>
+*/
+
+#ifndef MENSURAL_LIGATURE_HH
+#define MENSURAL_LIGATURE_HH
+
+#include "lily-proto.hh"
+#include "lily-guile.hh"
+
+/*
+ * These are all possible mensural ligature primitives.
+ */
+#define MLP_BB    0x01 // flexa with left downward cauda (for Brevis-Brevis)
+#define MLP_sc    0x02 // last head of asc. sine proprietate cum perfectione
+                      // (i.e. brevis head with downward right cauda)
+#define MLP_ss    0x04 // last head of asc. sine proprietate sine perfectione
+                      // (i.e. pure brevis head)
+#define MLP_cs    0x08 // last head of asc. cum proprietate sine perfectione
+                      // (i.e. brevis head with downward left cauda)
+#define MLP_SS    0x10 // flexa with left upward cauda (for Semibr.-Semibr.)
+#define MLP_LB    0x20 // core flexa (for Longa-Brevis)
+
+#define MLP_NONE  0x00 // no output
+#define MLP_SINGLE_HEAD        (MLP_sc | MLP_ss | MLP_cs)
+#define MLP_FLEXA      (MLP_BB | MLP_SS | MLP_LB)
+#define MLP_ANY                (MLP_FLEXA | MLP_SINGLE_HEAD)
+
+struct Mensural_ligature
+{
+  DECLARE_SCHEME_CALLBACK (brew_ligature_primitive, (SCM ));
+  DECLARE_SCHEME_CALLBACK (brew_molecule, (SCM ));
+  static bool has_interface (Grob*);
+};
+
+#endif /* MENSURAL_LIGATURE_HH */
diff --git a/lily/ligature-engraver.cc b/lily/ligature-engraver.cc
new file mode 100644 (file)
index 0000000..cda7f26
--- /dev/null
@@ -0,0 +1,196 @@
+/*   
+  ligature-engraver.cc -- implement Ligature_engraver
+  
+  source file of the GNU LilyPond music typesetter
+  
+  (c) 2002 Juergen Reuter <reuter@ipd.uka.de>
+  
+ */
+#include "ligature-engraver.hh"
+#include "ligature-head.hh"
+#include "spanner.hh"
+#include "score-engraver.hh"
+#include "rest.hh"
+#include "warn.hh"
+
+/*
+ * TODO: lyrics/melisma/syllables: there should be at most one
+ * syllable of lyrics per ligature (i.e. for the lyrics context, a
+ * ligature should count as a single note, regardless of how many
+ * heads the ligature consists of).
+ *
+ * TODO: currently, you have to add/remove the proper
+ * Ligature_engraver (Ligature_bracket_engraver,
+ * Mensural_ligature_engraver) to the proper translator
+ * (e.g. VoiceContext) to choose between various representations.
+ * Since adding/removing an engraver to a translator is a global
+ * action in the paper block, you can not mix various representations
+ * _within_ the same score.  Hence, for selecting a representation,
+ * one would rather like to have a property that can be set e.g. for
+ * several staves individually.  However, it seems that this approach
+ * would require to have a single, complicated Ligature_engraver that
+ * consists of all the code...  This needs further thoughts.
+ */
+Ligature_engraver::Ligature_engraver ()
+{
+  ligature_p_ = 0;
+  finished_ligature_p_ = 0;
+  reqs_drul_[LEFT] = reqs_drul_[RIGHT] = 0;
+  prev_start_req_ = 0;
+  last_bound = 0;
+  brew_ligature_primitive_proc = SCM_EOL;
+}
+
+bool
+Ligature_engraver::try_music (Music *m)
+{
+  if (Span_req *req_ = dynamic_cast<Span_req*> (m))
+    {
+      if (scm_equal_p (req_->get_mus_property ("span-type"),
+                      ly_str02scm ("abort")) == SCM_BOOL_T)
+       {
+         reqs_drul_[START] = 0;
+         reqs_drul_[STOP] = 0;
+         if (ligature_p_)
+           ligature_p_->suicide ();
+         ligature_p_ = 0;
+       }
+      else if (scm_equal_p (req_->get_mus_property ("span-type"),
+                           ly_str02scm ("ligature")) == SCM_BOOL_T)
+       {
+         Direction d = req_->get_span_dir ();
+         reqs_drul_[d] = req_;
+         return true;
+       }
+    }
+  return false;
+}
+
+Spanner *
+Ligature_engraver::create_ligature_spanner ()
+{
+  return new Spanner (SCM_EOL);
+}
+
+void
+Ligature_engraver::process_music ()
+{
+  if (reqs_drul_[STOP])
+    {
+      if (!ligature_p_)
+       reqs_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
+      else
+       {
+         if (!last_bound)
+           {
+             reqs_drul_[STOP]->origin ()->warning (_ ("no right bound"));
+           }
+         else
+           {
+             ligature_p_->set_bound (RIGHT, last_bound);
+           }
+       }
+      prev_start_req_ = 0;
+      finished_ligature_p_ = ligature_p_;
+      ligature_p_ = 0;
+    }
+  last_bound = unsmob_grob (get_property ("currentMusicalColumn"));
+
+  if (ligature_p_)
+    {
+      // TODO: maybe forbid breaks only if not transcribing
+      top_engraver ()->forbid_breaks ();
+    }
+  if (reqs_drul_[START])
+    {
+      if (ligature_p_)
+       {
+         reqs_drul_[START]->origin ()->warning (_ ("already have a ligature"));
+         return;
+       }
+
+      prev_start_req_ = reqs_drul_[START];
+      ligature_p_ = create_ligature_spanner ();
+      brew_ligature_primitive_proc =
+       ligature_p_->get_grob_property ("ligature-primitive-callback");
+      if (brew_ligature_primitive_proc == SCM_EOL)
+       {
+         warning ("Ligature_engraver: ligature-primitive-callback undefined");
+       }
+
+      Grob *bound = unsmob_grob (get_property ("currentMusicalColumn"));
+      if (!bound)
+       {
+         reqs_drul_[START]->origin ()->warning (_ ("no left bound"));
+       }
+      else
+       {
+         ligature_p_->set_bound (LEFT, bound);
+       }
+
+      ligature_start_mom_ = now_mom ();
+      
+      announce_grob(ligature_p_, reqs_drul_[START]->self_scm());
+    }
+}
+
+void
+Ligature_engraver::start_translation_timestep ()
+{
+  reqs_drul_[START] = 0;
+  reqs_drul_[STOP] = 0;
+}
+
+void
+Ligature_engraver::try_stop_ligature ()
+{
+  if (finished_ligature_p_)
+    {
+      typeset_grob (finished_ligature_p_);
+      finished_ligature_p_ = 0;
+    }
+}
+
+void
+Ligature_engraver::stop_translation_timestep ()
+{
+  try_stop_ligature ();
+}
+
+void
+Ligature_engraver::finalize ()
+{
+  try_stop_ligature ();
+  if (ligature_p_)
+    {
+      prev_start_req_->origin ()->warning (_ ("unterminated ligature"));
+      ligature_p_->suicide ();
+    }
+}
+
+void
+Ligature_engraver::acknowledge_grob (Grob_info info)
+{
+  if (ligature_p_)
+    {
+      if (Ligature_head::has_interface (info.grob_l_))
+       {
+         info.grob_l_->set_grob_property ("ligature-primitive-callback",
+                                          brew_ligature_primitive_proc);
+       }
+      else if (Rest::has_interface (info.grob_l_))
+       {
+         info.music_cause ()->origin ()->warning (_ ("ligature may not contain rest; ignoring rest"));
+         prev_start_req_->origin ()->warning (_ ("ligature was started here"));
+         // TODO: maybe better should stop ligature here rather than
+         // ignoring the rest?
+       }
+    }
+}
+
+ENTER_DESCRIPTION(Ligature_engraver,
+/* descr */       "Abstract class; a concrete subclass handles Ligature_requests by engraving Ligatures in a concrete style.",
+/* creats*/       "Ligature",
+/* acks  */       "ligature-head-interface rest-interface",
+/* reads */       "",
+/* write */       "");
diff --git a/lily/ligature-head.cc b/lily/ligature-head.cc
new file mode 100644 (file)
index 0000000..5b6445a
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+  ligature-head.cc -- implement Ligature_head
+
+  source file of the GNU LilyPond music typesetter
+
+  (c) 2002 Juergen Reuter <reuter@ipd.uka.de>
+*/
+
+#include "ligature-head.hh"
+#include "item.hh"
+#include "note-head.hh"
+#include "warn.hh"
+
+/*
+ * TODO: in scm/grob-description.scm, LigatureHead must contain value
+ * "rhythmic-head-interface" in the interfaces list.  Otherwise, text
+ * scripts (such as fermata) are horizontally aligned with the end of
+ * the ligature rather than with the associated head.  Why?
+ *
+ * TODO: if properties font-family and style are not set properly
+ * (e.g. by a user erronously setting font-family to #'music),
+ * lilypond currently crashes with the message: "lilypond:
+ * ../flower/include/interval.hh:28: Real Interval_t<double>::center()
+ * const: Assertion `!empty_b ()' failed.".  The code of this class
+ * should be clever enough to foresee a potential crash, print a
+ * warning, and supply sensible default values that avoid the crash.
+ */
+MAKE_SCHEME_CALLBACK (Ligature_head,brew_molecule,1);
+SCM
+Ligature_head::brew_molecule (SCM smob)  
+{
+  Grob *me = unsmob_grob (smob);
+  SCM brew_ligature_primitive_proc =
+    me->get_grob_property ("ligature-primitive-callback");
+  if (brew_ligature_primitive_proc != SCM_EOL)
+    {
+      return gh_call1 (brew_ligature_primitive_proc, smob);
+    }
+  else
+    {
+      warning ("Ligature_head: ligature-primitive-callback undefined -> resorting to Note_head::brew_molecule");
+      return Note_head::brew_molecule (smob);
+    }
+}
+
+ADD_INTERFACE (Ligature_head,"ligature-head-interface","Ligature head","");
diff --git a/lily/mensural-ligature-engraver.cc b/lily/mensural-ligature-engraver.cc
new file mode 100644 (file)
index 0000000..9adb710
--- /dev/null
@@ -0,0 +1,583 @@
+/*
+  mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver
+  
+  source file of the GNU LilyPond music typesetter
+  
+  (C) 2002 Juergen Reuter <reuter@ipd.uka.de>
+ */
+
+#include "mensural-ligature.hh"
+#include "ligature-engraver.hh"
+#include "musical-request.hh"
+#include "warn.hh"
+#include "item.hh"
+#include "spanner.hh"
+#include "rod.hh"
+#include "paper-column.hh"
+#include "note-column.hh"
+#include "rhythmic-head.hh"
+#include "note-head.hh"
+#include "staff-symbol-referencer.hh"
+#include "paper-def.hh"
+#include "font-interface.hh"
+
+/*
+ * TODO: local accidentals: collect accidentals that occur within a
+ * ligature and put them before the ligature.  If an accidental
+ * changes within a ligature, print a warning (user error) and ignore
+ * any further accidental for that pitch within that ligature
+ * (actually, in such a case, the user should split the ligature into
+ * two separate ligatures).  Similarly, any object that, in ordinary
+ * notation, may be put to the left or to the right of a
+ * note-head/ligature-head, should be collected and put before or
+ * after the ligature.
+ *
+ * TODO: make spacing more robust: do not screw up spacing if user
+ * erroneously puts rest in ligature.
+ *
+ * TODO: My resources on Franco of Cologne's rules claim that his
+ * rules map ligature<->mensural timing in a non-ambigous way, but in
+ * fact, as presented in these resources, the rules become ambigous as
+ * soon as there appear durations other than breves within a ligature
+ * with more than two heads (ligatura ternaria etc.).  Hence, the
+ * below implementation is an approximation of what I think the rules
+ * could look like if forced to be non-ambigous.  This should be
+ * further investigated.
+ *
+ * TODO: The automat is quite complicated, and its design is error
+ * prone (and most probably, it behaves wrong for some very special
+ * cases).  Maybe we can find a better paradigm for modelling Franco
+ * of Cologne's rules?
+ *
+ * TODO: dotted heads: when applying Franco of Cologne's mapping, put
+ * dots *above* (rather than after) affected ligature heads.
+ *
+ * TODO: prohibit multiple voices within a ligature.
+ *
+ * TODO: for each ligature, add Rod that represents the total length
+ * of the ligature (to preemptively avoid collision with adjacent
+ * notes); or maybe just additionally create a mensural-ligature grob
+ * (via Mensural_ligature::brew_molecule(SCM)) that just consists of a
+ * bounding box around all primitives of the ligature.
+ *
+ * TODO: enhance robustness: in case of an illegal ligature (e.g. the
+ * user requests for a ligature that contains a minima or STATE_ERROR
+ * is reached), automatically break the ligature into smaller, valid
+ * pieces.
+ *
+ * TODO: In the future, there will be further ligature engravers
+ * implemented, such as a Vaticana_ligature_engraver.  There will be
+ * redundant code between these engravers and the
+ * Mensural_ligature_engraver.  In particular these are functions
+ * set_column_l_, fold_up_primitives, join_primitives, and
+ * ackowledge_grob; further the code for handling accidentals.  It is
+ * not appropriate to put these things into Ligature_engraver, since,
+ * for example, Ligature_bracket_engraver does not share any of this
+ * code.  Hence, we might to introduce a further subclass of
+ * Ligature_engraver which serves as super class for
+ * Mensural_ligature_engraver, Vaticana_ligature_engraver, among
+ * others.
+ */
+class Mensural_ligature_engraver : public Ligature_engraver
+{
+  Real distance_f_;
+  Array<Grob_info> primitives_arr_;
+
+protected:
+  virtual void acknowledge_grob (Grob_info);
+  virtual void try_stop_ligature ();
+  virtual Spanner *create_ligature_spanner ();
+
+public:
+  TRANSLATOR_DECLARATIONS(Mensural_ligature_engraver);
+
+private:
+  int apply_transition (int state, int input, int i);
+  void transform_heads ();
+  void propagate_properties ();
+  void fold_up_primitives ();
+  void join_primitives ();
+  void set_column_l (Item *item, Paper_column *new_col);
+};
+
+
+Mensural_ligature_engraver::Mensural_ligature_engraver ()
+{
+  distance_f_ = 0;
+}
+
+Spanner *
+Mensural_ligature_engraver::create_ligature_spanner ()
+{
+  distance_f_ = 0;
+  return new Spanner (get_property ("MensuralLigature"));
+}
+
+/*
+ * TODO: move this function to class Item?
+ */
+void
+Mensural_ligature_engraver::set_column_l (Item *item, Paper_column *column)
+{
+  Item *parent = dynamic_cast<Item*> (item->get_parent (X_AXIS));
+  if (!parent)
+    {
+      programming_error ("failed tweaking paper column in ligature");
+      return;
+    }
+
+  String name = parent->name ();
+  if (!String::compare_i (name, "PaperColumn"))
+    {
+      // Change column not only for targeted item (NoteColumn), but
+      // also for all associated grobs (NoteSpacing, SeparationItem).
+      Grob *sl = Staff_symbol_referencer::staff_symbol_l (item);
+      for (SCM tail = parent->get_grob_property ("elements");
+          gh_pair_p (tail);
+          tail = ly_cdr (tail))
+       {
+         Item *sibling = unsmob_item (ly_car (tail));
+         if ((sibling) &&
+             (Staff_symbol_referencer::staff_symbol_l (sibling) == sl))
+           {
+             sibling->set_parent (column, X_AXIS);
+           }
+       }
+    }
+  else
+    {
+      set_column_l (parent, column);
+    }
+}
+
+/*
+ * The following lines implement a finite state automat.  Given a
+ * sequence of durations (Longa, Brevis, Semibrevis) or
+ * end-of-ligature-request as input, the automat outputs a sequence of
+ * requests for grobs that form a proper ligature.
+ */
+
+/*
+ * This enumeration represents the set of possible input values to the
+ * automat.  There may (potentially) be any sequence of Longa, Brevis,
+ * and Semibrevis duration symbols fed into the automat, with a final
+ * EndOfLigature symbol to terminate the ligature.  Other durations
+ * are explicitly prohibited.  Depending on the note's pitch of the
+ * preceding and the current input, the melodic line may be ascending
+ * or descending.  Per definition, the melodic line must either ascend
+ * or descend, because if the pitches were twice the same, the two
+ * notes would be merged into a single one (as long as not resulting
+ * in a prohibited duration).  In the case of the EndOfLigature
+ * symbol, the melodic line is undefined (but we still have ascending
+ * and descending case for the sake of consistency, making the automat
+ * simpler).
+ */
+enum Ligature_input
+{
+  // Ascending/Descending Longa/Brevis/Semibrevis/EndOfLigature
+  INPUT_AL = 0,
+  INPUT_DL,
+  INPUT_AB,
+  INPUT_DB,
+  INPUT_AS,
+  INPUT_DS,
+  INPUT_AE,
+  INPUT_DE,
+};
+
+/*
+ * This enumeration represents all possible internal states of the
+ * automat.  Besides the generic states START, ERROR, and END, the
+ * remaining states L, B, S, and SS describe pending values from the
+ * sequence of input values that have not yet been transformed to
+ * proper output values, including the melodic direction
+ * (ascending/descending) for state L.
+ */
+enum Ligature_state
+{
+  // aL = ascending Longa, dL descending Longa, B = Brevis, S =
+  // Semibrevis, SS = 2 Semibreves
+  STATE_START = 0,
+  STATE_aL,
+  STATE_dL,
+  STATE_B,
+  STATE_S,
+  STATE_SS,
+  STATE_ERROR,
+  STATE_END,
+};
+
+/*
+ * The following array represents the transitions of the automat:
+ * given some state and input, it maps to a new state, according (with
+ * the limitations as described above) to the rules of Franco of
+ * Cologne.
+ */
+const int/*new state*/ transition_state[/*old state*/][8/*input*/] =
+{
+  {STATE_aL,    STATE_dL,    STATE_B,     STATE_B,
+   STATE_S,     STATE_S,     STATE_ERROR, STATE_ERROR}, // was: STATE_START
+  {STATE_aL,    STATE_dL,    STATE_B,     STATE_START,
+   STATE_ERROR, STATE_ERROR, STATE_END,   STATE_END},   // was: STATE_aL
+  {STATE_aL,    STATE_dL,    STATE_B,     STATE_START,
+   STATE_ERROR, STATE_ERROR, STATE_END,   STATE_END},   // was: STATE_dL
+  {STATE_aL,    STATE_dL,    STATE_B,     STATE_START,
+   STATE_ERROR, STATE_ERROR, STATE_END,   STATE_END},   // was: STATE_B
+  {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR,
+   STATE_SS,    STATE_SS,    STATE_ERROR, STATE_ERROR}, // was: STATE_S
+  {STATE_aL,    STATE_dL,    STATE_B,     STATE_B,
+   STATE_S,     STATE_S,     STATE_END,   STATE_END},   // was: STATE_SS
+  {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR,
+   STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR}, // was: STATE_ERROR
+  {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR,
+   STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR}, // was: STATE_END
+};
+
+/*
+ * The following array represents the output of the automat while
+ * switching from one state to another: given some state and input, it
+ * maps to the output produced when switching to the next state,
+ * according (with the limitations as described above) to the rules of
+ * Franco of Cologne.
+ */
+const int/*output*/ transition_output[/*old state*/][8/*input*/] =
+{
+  {MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE,
+   MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE}, // was: STATE_START
+  {MLP_sc,    MLP_ss,    MLP_sc,    MLP_LB,
+   MLP_NONE,  MLP_NONE,  MLP_sc,    MLP_sc},   // was: STATE_aL
+  {MLP_sc,    MLP_ss,    MLP_sc,    MLP_LB,
+   MLP_NONE,  MLP_NONE,  MLP_ss,    MLP_ss},   // was: STATE_dL
+  {MLP_ss,    MLP_cs,    MLP_ss,    MLP_BB,
+   MLP_NONE,  MLP_NONE,  MLP_ss,    MLP_ss} ,  // was: STATE_B
+  {MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE,
+   MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE}, // was: STATE_S
+  {MLP_SS,    MLP_SS,    MLP_SS,    MLP_SS,
+   MLP_SS,    MLP_SS,    MLP_SS,    MLP_SS} ,  // was: STATE_SS
+  {MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE,
+   MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE}, // was: STATE_ERROR
+  {MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE,
+   MLP_NONE,  MLP_NONE,  MLP_NONE,  MLP_NONE}, // was: STATE_END
+};
+
+int
+Mensural_ligature_engraver::apply_transition (int state, int input, int i)
+{
+  int output = transition_output[state][input];
+  Item *last_last_primitive = (i > 1) ?
+    dynamic_cast<Item*> (primitives_arr_[i-2].grob_l_) : 0;
+  Item *last_primitive = (i > 0) ?
+    dynamic_cast<Item*> (primitives_arr_[i-1].grob_l_) : 0;
+  Item *primitive = (i < primitives_arr_.size ()) ?
+    dynamic_cast<Item*> (primitives_arr_[i].grob_l_) : 0;
+  switch (output)
+    {
+      case MLP_NONE:
+       // skip note head, expecting a primitive with two note heads
+       break;
+      case MLP_sc:
+      case MLP_ss:
+      case MLP_cs:
+       // primitive with single note head
+       if (!last_primitive)
+         {
+           programming_error ("last_primitive undefined");
+           break;
+         }
+       last_primitive->set_grob_property ("primitive", gh_int2scm (output));
+       break;
+      case MLP_BB:
+      case MLP_LB:
+       // primitive with two note heads
+       if (!last_primitive)
+         {
+           programming_error ("last_primitive undefined");
+           break;
+         }
+       if (!primitive)
+         {
+           programming_error ("primitive undefined");
+           break;
+         }
+       last_primitive->set_grob_property ("primitive", gh_int2scm (output));
+       primitive->set_grob_property ("primitive", gh_int2scm (MLP_NONE));
+       break;
+      case MLP_SS:
+       // delayed primitive with two note heads
+       if (!last_last_primitive)
+         {
+           programming_error ("last_last_primitive undefined");
+           break;
+         }
+       if (!last_primitive)
+         {
+           programming_error ("last_primitive undefined");
+           break;
+         }
+       last_last_primitive->set_grob_property ("primitive", gh_int2scm (output));
+       last_primitive->set_grob_property ("primitive", gh_int2scm (MLP_NONE));
+       break;
+      default:
+       programming_error (_f ("unexpected case fall-through"));
+       break;
+    }
+  return transition_state[state][input];
+}
+
+void
+Mensural_ligature_engraver::transform_heads ()
+{
+  if (primitives_arr_.size () < 2)
+    {
+      warning (_f ("ligature with less than 2 heads -> skipping"));
+      return;
+    }
+  int state = STATE_START;
+  Pitch last_pitch, pitch;
+  bool have_last_pitch = 0, have_pitch = 0;
+  for (int i = 0; i < primitives_arr_.size (); i++) {
+    last_pitch = pitch;
+    have_last_pitch = have_pitch;
+    Grob_info info = primitives_arr_[i];
+    int duration_log =
+      Rhythmic_head::balltype_i (dynamic_cast<Item*> (info.grob_l_));
+    Note_req *nr = dynamic_cast<Note_req*> (info.music_cause ());
+    if (!nr)
+      {
+       info.music_cause ()->origin ()->warning (_f ("can not determine pitch of ligature primitive -> skipping"));
+       i++;
+       state = STATE_START;
+       have_pitch = 0;
+       continue;
+      }
+    else
+      {
+       pitch = *unsmob_pitch (nr->get_mus_property ("pitch"));
+       have_pitch = 1;
+      }
+
+    int delta_pitch;
+
+    if (!have_last_pitch)
+      {
+       delta_pitch = 0; // first pitch; delta undefined
+      }
+    else
+      {
+       delta_pitch = (pitch.steps () - last_pitch.steps ());
+       if (Pitch::compare (last_pitch, pitch) == 0)
+         {
+           info.music_cause ()->origin ()->warning (_f ("prime interval within ligature -> skipping"));
+           i++;
+           state = STATE_START;
+           have_pitch = 0;
+           continue;
+         }
+      }
+
+    if ((duration_log < -2) || (duration_log > 0))
+      {
+       info.music_cause ()->origin ()->warning (_f ("mensural ligature: duration none of L, B, S -> skipping"));
+       i++;
+       state = STATE_START;
+       have_pitch = 0;
+       continue;
+      }
+
+    int input = (duration_log + 2) * 2 + ((delta_pitch < 0) ? 1 : 0);
+    state = apply_transition (state, input, i);
+    // TODO: if (state == STATE_ERROR) { ... }
+  }
+
+  state = apply_transition (state, INPUT_AE, primitives_arr_.size ());
+  // TODO: if (state == STATE_ERROR) { ... }
+}
+
+void set_delta_pitch (Item *primitive, Grob_info info1, Grob_info info2)
+{
+  Note_req *nr1 = dynamic_cast<Note_req*> (info1.music_cause ());
+  Note_req *nr2 = dynamic_cast<Note_req*> (info2.music_cause ());
+  Pitch pitch1 = *unsmob_pitch (nr1->get_mus_property ("pitch"));
+  Pitch pitch2 = *unsmob_pitch (nr2->get_mus_property ("pitch"));
+  int delta_pitch = (pitch2.steps () - pitch1.steps ());
+  primitive->set_grob_property ("delta-pitch", gh_int2scm (delta_pitch));
+}
+
+/*
+ * A MensuralLigature grob consists of a bunch of LigatureHead grobs
+ * that are glued together.  It (a) does make sense to change
+ * properties like thickness or flexa-width from one head to the next
+ * within a ligature (this would totally screw up alignment), and (b)
+ * some of these properties (like flexa-width) are specific to
+ * e.g. the MensuralLigature (as in contrast to e.g. LigatureBracket),
+ * and therefore should not be handled in the generic LigatureHead
+ * (which is also used by LigatureBracket).  Therefore, we let the
+ * user control these properties via the concrete Ligature grob (like
+ * MensuralLigature) and then copy these properties as necessary to
+ * each of the LigatureHead grobs.  This is what
+ * propagate_properties() does.
+ */
+void
+Mensural_ligature_engraver::propagate_properties ()
+{
+  SCM thickness_scm =
+    finished_ligature_p_->get_grob_property ("thickness");
+  Real thickness = (thickness_scm != SCM_EOL) ?
+    gh_scm2double (thickness_scm) : 1.4;
+  thickness *= finished_ligature_p_->paper_l ()->get_var ("linethickness");
+
+  /*
+   * FIXME: Since character "noteheads--1mensural" is defined in
+   * parmesan font only, the right-hand expression in the
+   * following assignment evaluates to a width of 0.0, in case
+   * font-family of finished_ligature_p_ is _not_ set to "ancient"
+   * (by default, it is; see grob properties of MensuralLigature
+   * in scm/grob-description.scm).  This may arise severe problems
+   * in the future when switching between fonts (e.g. mensural
+   * versus neo-mensural).
+   */
+  Real head_width =
+    Font_interface::get_default_font (finished_ligature_p_)->
+    find_by_name ("noteheads--1mensural").extent (X_AXIS).length ();
+  if (head_width == 0.0)
+    {
+      programming_error ("Mensural_ligature_engraver: failed evaluating head_width (most probably a font-family selection problem)");
+    }
+
+  SCM flexa_width_scm =
+    finished_ligature_p_->get_grob_property ("flexa-width");
+  Real flexa_width = (flexa_width_scm != SCM_EOL) ?
+    gh_scm2double (flexa_width_scm) : 2.0;
+  flexa_width *= Staff_symbol_referencer::staff_space (finished_ligature_p_);
+
+  Real half_flexa_width = 0.5 * (flexa_width + thickness);
+
+  for (int i = 0; i < primitives_arr_.size (); i++)
+    {
+      Item *primitive = dynamic_cast<Item*> (primitives_arr_[i].grob_l_);
+      int output = gh_scm2int (primitive->get_grob_property ("primitive"));
+      primitive->set_grob_property ("thickness",
+                                   gh_double2scm (thickness));
+      switch (output) {
+       case MLP_NONE:
+         primitive->set_grob_property ("head-width",
+                                       gh_double2scm (half_flexa_width));
+         break;
+       case MLP_sc:
+       case MLP_ss:
+       case MLP_cs:
+         primitive->set_grob_property ("head-width",
+                                       gh_double2scm (head_width));
+         break;
+       case MLP_BB:
+       case MLP_LB:
+       case MLP_SS:
+         primitive->set_grob_property ("head-width",
+                                       gh_double2scm (half_flexa_width));
+         primitive->set_grob_property ("flexa-width",
+                                       gh_double2scm (flexa_width));
+         set_delta_pitch (primitive,
+                          primitives_arr_[i], primitives_arr_[i+1]);
+         break;
+       default:
+         programming_error (_f ("unexpected case fall-through"));
+         break;
+      }
+    }
+}
+
+void
+Mensural_ligature_engraver::fold_up_primitives ()
+{
+  Item *first = 0;
+  for (int i = 0; i < primitives_arr_.size (); i++)
+    {
+      Item *current = dynamic_cast<Item*> (primitives_arr_[i].grob_l_);
+      if (i == 0)
+       {
+         first = current;
+       }
+
+      set_column_l (current, first->column_l ());
+
+      if (i > 0)
+       {
+#if 0
+         Rod r;
+         r.distance_f_ = distance_f_;
+         r.item_l_drul_[LEFT] = first;
+         r.item_l_drul_[RIGHT] = current;
+         r.add_to_cols ();
+#endif
+         current->translate_axis (distance_f_, X_AXIS);
+       }
+
+      distance_f_ +=
+       gh_scm2double (current->get_grob_property ("head-width")) -
+       gh_scm2double (current->get_grob_property ("thickness"));
+    }
+}
+
+void
+Mensural_ligature_engraver::join_primitives ()
+{
+  Pitch last_pitch;
+  for (int i = 0; i < primitives_arr_.size (); i++)
+    {
+      Grob_info info = primitives_arr_[i];
+      Note_req *nr = dynamic_cast<Note_req*> (info.music_cause ());
+      Pitch pitch = *unsmob_pitch (nr->get_mus_property ("pitch"));
+      if (i > 0)
+        {
+         Item *primitive = dynamic_cast<Item*> (info.grob_l_);
+         int output = gh_scm2int (primitive->get_grob_property ("primitive"));
+         if (output & MLP_ANY)
+           {
+             int delta_pitch = (pitch.steps () - last_pitch.steps ());
+             primitive->set_grob_property ("join-left",
+                                           gh_int2scm (delta_pitch));
+           }
+       }
+      last_pitch = pitch;
+    }
+}
+
+void
+Mensural_ligature_engraver::try_stop_ligature ()
+{
+  if (finished_ligature_p_)
+    {
+      transform_heads ();
+      propagate_properties ();
+      fold_up_primitives ();
+      join_primitives ();
+
+      for (int i = 0; i < primitives_arr_.size (); i++)
+       {
+         typeset_grob (primitives_arr_[i].grob_l_);
+       }
+
+      primitives_arr_.clear ();
+      finished_ligature_p_ = 0;
+    }
+}
+
+void
+Mensural_ligature_engraver::acknowledge_grob (Grob_info info)
+{
+  Ligature_engraver::acknowledge_grob (info);
+  if (ligature_p_)
+    {
+      if (Note_head::has_interface (info.grob_l_))
+       {
+         primitives_arr_.push (info);
+       }
+    }
+}
+
+ENTER_DESCRIPTION(Mensural_ligature_engraver,
+/* descr */       "Handles Mensural_ligature_requests by glueing special ligature heads together.",
+/* creats*/       "MensuralLigature",
+/* acks  */       "ligature-head-interface note-head-interface rest-interface",
+/* reads */       "",
+/* write */       "");
diff --git a/lily/mensural-ligature.cc b/lily/mensural-ligature.cc
new file mode 100644 (file)
index 0000000..3d6a4d1
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+  mensural-ligature.cc -- implement Mensural_ligature
+  
+  source file of the GNU LilyPond music typesetter
+  
+  (c) 2002 Juergen Reuter <reuter@ipd.uka.de>
+*/
+
+#include <math.h>
+#include "item.hh"
+#include "mensural-ligature.hh"
+#include "font-interface.hh"
+#include "molecule.hh"
+#include "lookup.hh"
+#include "staff-symbol-referencer.hh"
+#include "note-head.hh"
+#include "paper-def.hh"
+#include "warn.hh"
+
+/*
+ * TODO: divide this function into mensural and neo-mensural style.
+ *
+ * TODO: move this function to class Lookup?
+ */
+Molecule
+brew_flexa (Grob *me,
+           Real interval,
+           bool solid,
+           Real width,
+           Real thickness,
+           bool add_stem,
+           Direction stem_direction)
+{
+  Real staff_space = Staff_symbol_referencer::staff_space (me);
+  Real height = 0.6 * staff_space;
+  Molecule molecule = Molecule ();
+
+  if (add_stem)
+    {
+      bool consider_interval =
+       stem_direction * interval > 0.0;
+
+      Interval stem_box_x (0, thickness);
+      Interval stem_box_y;
+
+      if (consider_interval)
+        {
+         Real y_length = max (interval/2.0*staff_space, 1.2*staff_space);
+         stem_box_y = Interval (0, y_length);
+       }
+      else
+       stem_box_y = Interval (0, staff_space);
+
+      Real y_correction =
+       (stem_direction == UP) ?
+       +0.5*height :
+       -0.5*height - stem_box_y.length();
+
+      Box stem_box (stem_box_x, stem_box_y);
+      Molecule stem = Lookup::filledbox (stem_box);
+      stem.translate_axis (y_correction, Y_AXIS);
+      molecule.add_molecule(stem);
+    }
+
+  Real slope = (interval / 2.0 * staff_space) / width;
+
+  // Compensate optical illusion regarding vertical position of left
+  // and right endings due to slope.
+  Real ypos_correction = -0.1*staff_space * sign(slope);
+  Real slope_correction = 0.2*staff_space * sign(slope);
+  Real corrected_slope = slope + slope_correction/width;
+
+  if (solid)
+    {
+      Molecule solid_head =
+       Lookup::horizontal_slope (width, corrected_slope, height);
+      molecule.add_molecule (solid_head);
+    }
+  else // outline
+    {
+      Molecule left_edge =
+       Lookup::horizontal_slope (thickness, corrected_slope, height);
+      molecule.add_molecule(left_edge);
+
+      Molecule right_edge =
+       Lookup::horizontal_slope (thickness, corrected_slope, height);
+      right_edge.translate_axis (width-thickness, X_AXIS);
+      right_edge.translate_axis (corrected_slope * (width-thickness), Y_AXIS);
+      molecule.add_molecule(right_edge);
+
+      Molecule bottom_edge =
+       Lookup::horizontal_slope (width, corrected_slope, thickness);
+      bottom_edge.translate_axis (-0.5*height, Y_AXIS);
+      molecule.add_molecule (bottom_edge);
+
+      Molecule top_edge =
+       Lookup::horizontal_slope (width, corrected_slope, thickness);
+      top_edge.translate_axis (+0.5*height, Y_AXIS);
+      molecule.add_molecule (top_edge);
+    }
+  molecule.translate_axis (ypos_correction, Y_AXIS);
+  return molecule;
+}
+
+void
+add_ledger_lines (Grob *me, Molecule *out, int pos, Real offs,
+                 bool ledger_take_space)
+{
+  int interspaces = Staff_symbol_referencer::line_count (me)-1;
+  if (abs (pos) - interspaces > 1)
+    {
+      Interval hd = out->extent (X_AXIS);
+      Real left_ledger_protusion = hd.length ()/4;
+      Real right_ledger_protusion = left_ledger_protusion;
+
+      Interval l_extents = Interval (hd[LEFT] - left_ledger_protusion,
+                                    hd[RIGHT] + right_ledger_protusion);
+      Molecule ledger_lines =
+       Note_head::brew_ledger_lines (me, pos, interspaces,
+                                     l_extents,
+                                     ledger_take_space);
+      ledger_lines.translate_axis (offs, Y_AXIS);
+      out->add_molecule (ledger_lines);
+    }
+}
+
+Molecule
+internal_brew_primitive (Grob *me, bool ledger_take_space)
+{
+  SCM primitive_scm = me->get_grob_property ("primitive");
+  if (primitive_scm == SCM_EOL)
+    {
+      programming_error ("Mensural_ligature: undefined primitive -> ignoring grob");
+      return Molecule ();
+    }
+
+  Molecule out;
+  int primitive = gh_scm2int (primitive_scm);
+  int delta_pitch = 0;
+  Real thickness = 0.0;
+  Real flexa_width = 0.0;
+  Real staff_space = Staff_symbol_referencer::staff_space (me);
+  if (primitive & MLP_ANY)
+    {
+      SCM thickness_scm = me->get_grob_property ("thickness");
+      if (thickness_scm != SCM_EOL)
+       {
+         thickness = gh_scm2double (thickness_scm);
+       }
+      else
+       {
+         programming_error (_f ("Mensural_ligature: thickness undefined on flexa %d; assuming 1.4", primitive));
+         thickness = 1.4 * me->paper_l ()->get_var ("linethickness");
+       }
+    }
+
+  if (primitive & MLP_FLEXA)
+    {
+      SCM delta_pitch_scm = me->get_grob_property ("delta-pitch");
+      if (delta_pitch_scm != SCM_EOL)
+       {
+         delta_pitch = gh_scm2int (delta_pitch_scm);
+       }
+      else
+       {
+         programming_error (_f ("Mensural_ligature: delta-pitch undefined on flexa %d; assuming 0", primitive));
+         delta_pitch = 0;
+       }
+
+      SCM flexa_width_scm = me->get_grob_property ("flexa-width");
+      if (flexa_width_scm != SCM_EOL)
+       {
+         flexa_width = gh_scm2double (flexa_width_scm);
+       }
+      else
+       {
+         programming_error (_f ("Mensural_ligature: flexa-width undefined on flexa %d; assuming 2.0", primitive));
+         flexa_width = 2.0 * staff_space;
+       }
+    }
+
+  switch (primitive)
+    {
+      case MLP_NONE:
+       return Molecule();
+      case MLP_BB:
+       out = brew_flexa (me, delta_pitch, false,
+                         flexa_width, thickness, true, DOWN);
+       break;
+      case MLP_sc:
+       out = Font_interface::get_default_font (me)->find_by_name ("noteheads--2mensural");
+       break;
+      case MLP_ss:
+       out = Font_interface::get_default_font (me)->find_by_name ("noteheads--1mensural");
+       break;
+      case MLP_cs:
+       out = Font_interface::get_default_font (me)->find_by_name ("noteheads-lmensural");
+       break;
+      case MLP_SS:
+       out = brew_flexa (me, delta_pitch, false,
+                         flexa_width, thickness, true, UP);
+       break;
+      case MLP_LB:
+       out = brew_flexa (me, delta_pitch, false,
+                         flexa_width, thickness, false, CENTER);
+       break;
+      default:
+       programming_error (_f ("Mensural_ligature: unexpected case fall-through"));
+       return Molecule ();
+    }
+
+  SCM join_left_scm = me->get_grob_property ("join-left");
+  if (join_left_scm != SCM_EOL)
+    {
+      int join_left = gh_scm2int (join_left_scm);
+      if (!join_left)
+       programming_error (_f ("Menusral_ligature: (join_left == 0)"));
+      Real blotdiameter = (me->paper_l ()->get_var ("blotdiameter"));
+      Interval x_extent = Interval (0, thickness);
+      Interval y_extent = (join_left > 0) ?
+       Interval (-join_left * 0.5 * staff_space, 0) :
+       Interval (0, -join_left * 0.5 * staff_space);
+      Box stem_box (x_extent, y_extent);
+
+      Molecule stem =
+       Lookup::roundfilledbox (stem_box, blotdiameter);
+      out.add_molecule (stem);
+    }
+
+  int pos = (int)rint (Staff_symbol_referencer::position_f (me));
+  add_ledger_lines(me, &out, pos, 0, ledger_take_space);
+  if (primitive & MLP_FLEXA)
+    {
+      pos += delta_pitch;
+      add_ledger_lines(me, &out, pos, 0.5*delta_pitch, ledger_take_space);
+    }
+
+  return out;
+}
+
+MAKE_SCHEME_CALLBACK (Mensural_ligature, brew_ligature_primitive, 1);
+SCM
+Mensural_ligature::brew_ligature_primitive (SCM smob)
+{
+  Grob *me = unsmob_grob (smob);
+  return internal_brew_primitive (me, false).smobbed_copy ();
+}
+
+MAKE_SCHEME_CALLBACK (Mensural_ligature, brew_molecule, 1);
+SCM
+Mensural_ligature::brew_molecule (SCM smob)
+{
+  return SCM_EOL;
+}
+
+ADD_INTERFACE(Mensural_ligature, "mensural-ligature-interface",
+             "A mensural ligature",
+             "thickness flexa-width");