source file of the GNU LilyPond music typesetter
- (c) 2002 Juergen Reuter <reuter@ipd.uka.de>
+ (c) 2002--2004 Juergen Reuter <reuter@ipd.uka.de>
*/
#include "ligature-engraver.hh"
-#include "ligature-head.hh"
#include "spanner.hh"
#include "score-engraver.hh"
+#include "note-head.hh"
#include "rest.hh"
#include "warn.hh"
+#include "translator-group.hh"
+
+/*
+ * This abstract class provides the general framework for ligatures of
+ * any kind. It cares for handling start/stop ligatures requests and
+ * collecting all noteheads inbetween, but delegates creation of a
+ * ligature spanner for each start/stop pair and typesetting of the
+ * ligature spanner to a concrete subclass.
+ *
+ * A concrete ligature engraver must subclass this class and provide
+ * functions create_ligature_spanner () and typeset_ligature
+ * (Spanner *, Array<Grob_info>). Subclasses of this class basically
+ * fall into two categories.
+ *
+ * The first category consists of engravers that engrave ligatures in
+ * a way that really deserves the name ligature. That is, they
+ * produce a single connected graphical object of fixed width,
+ * consisting of noteheads and other primitives. Space may be
+ * inserted only after each ligature, if necessary, but in no case
+ * between the primitives of the ligature. Accidentals have to be put
+ * to the left of the ligature, and not to the left of individual
+ * noteheads. Class Coherent_ligature_engraver is the common
+ * superclass for all of these engravers.
+ *
+ * The second category is for engravers that are relaxed in the sense
+ * that they do not require to produce a single connected graphical
+ * object. For example, in contemporary editions, ligatures are often
+ * marked, but otherwise use contemporary notation and spacing. In
+ * this category, there is currently only a single class,
+ * Ligature_bracket_engraver, which marks each ligature with a
+ * horizontal sqare bracket, but otherwise leaves the appearance
+ * untouched.
+ */
/*
* TODO: lyrics/melisma/syllables: there should be at most one
finished_ligature_ = 0;
reqs_drul_[LEFT] = reqs_drul_[RIGHT] = 0;
prev_start_req_ = 0;
- last_bound = 0;
+ last_bound_ = 0;
brew_ligature_primitive_proc = SCM_EOL;
}
bool
Ligature_engraver::try_music (Music *m)
{
- if (m->is_mus_type ("abort-event"))
- {
- reqs_drul_[START] = 0;
- reqs_drul_[STOP] = 0;
- if (ligature_)
- ligature_->suicide ();
- ligature_ = 0;
- }
- else if (m->is_mus_type ("ligature-event"))
+ if (m->is_mus_type ("ligature-event"))
{
Direction d = to_dir (m->get_mus_property ("span-direction"));
reqs_drul_[d] = m;
Spanner *
Ligature_engraver::create_ligature_spanner ()
{
- return new Spanner (SCM_EOL);
+ programming_error ("Ligature_engraver::create_ligature_spanner (): "
+ "this is an abstract method that should not be called, "
+ "but overridden by a subclass");
+ return 0;
+}
+
+/*
+ * This method should do something that comes close to the following
+ * .ly snippet:
+ *
+ * \property Voice.NoteHead \override #'print-function =
+ * < value of #'ligature-primitive-callback of Voice.NoteHead >
+ *
+ * TODO: What we are doing here on the c++ level, should actually be
+ * performed on the SCM level. However, I do not know how to teach
+ * lilypond to apply an \override and \revert on #'print-function,
+ * whenever lily encounters a \[ and \] in an .ly file, respectively.
+ * Also encounter, that lily should not crash if a user erronously
+ * nests \[ and \].
+ */
+void
+Ligature_engraver::override_stencil_callback ()
+{
+ SCM target_callback = ly_symbol2scm ("print-function");
+ SCM source_callback = ly_symbol2scm ("ligature-primitive-callback");
+ SCM noteHeadProperties = updated_grob_properties (daddy_trans_, ly_symbol2scm ("NoteHead"));
+ SCM value = ly_cdr (scm_sloppy_assq (source_callback, noteHeadProperties));
+ execute_pushpop_property (daddy_trans_, ly_symbol2scm ("NoteHead"),
+ target_callback, value);
+}
+
+/*
+ * This method should do something that comes close to the following
+ * .ly snippet:
+ *
+ * \property Voice.NoteHead \revert #'print-function
+ *
+ * TODO: What we are doing here on the c++ level, should actually be
+ * performed on the SCM level. However, I do not know how to teach
+ * lilypond to apply an \override and \revert on #'print-function,
+ * whenever lily encounters a \[ and \] in an .ly file, respectively.
+ * Also encounter, that lily should not crash if a user erronously
+ * nests \[ and \].
+ */
+void
+Ligature_engraver::revert_stencil_callback ()
+{
+ SCM symbol = ly_symbol2scm ("NoteHead");
+ SCM key = ly_symbol2scm ("print-function");
+ execute_pushpop_property (daddy_trans_, symbol, key, SCM_UNDEFINED);
}
void
if (reqs_drul_[STOP])
{
if (!ligature_)
- reqs_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
+ {
+ reqs_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
+ return;
+ }
+
+ if (!last_bound_)
+ {
+ reqs_drul_[STOP]->origin ()->warning (_ ("no right bound"));
+ }
else
{
- if (!last_bound)
- {
- reqs_drul_[STOP]->origin ()->warning (_ ("no right bound"));
- }
- else
- {
- ligature_->set_bound (RIGHT, last_bound);
- }
+ ligature_->set_bound (RIGHT, last_bound_);
}
+
prev_start_req_ = 0;
+ finished_primitives_ = primitives_;
finished_ligature_ = ligature_;
+ primitives_.clear ();
ligature_ = 0;
+ revert_stencil_callback ();
}
- last_bound = unsmob_grob (get_property ("currentMusicalColumn"));
+ last_bound_ = unsmob_grob (get_property ("currentMusicalColumn"));
if (ligature_)
{
// TODO: maybe forbid breaks only if not transcribing
top_engraver ()->forbid_breaks ();
}
+
if (reqs_drul_[START])
{
if (ligature_)
ligature_start_mom_ = now_mom ();
announce_grob(ligature_, reqs_drul_[START]->self_scm());
+ override_stencil_callback ();
}
}
void
-Ligature_engraver::start_translation_timestep ()
+Ligature_engraver::typeset_ligature (Spanner *, Array<Grob_info>)
{
- reqs_drul_[START] = 0;
- reqs_drul_[STOP] = 0;
+ programming_error ("Ligature_engraver::typeset_ligature (): "
+ "this is an abstract method that should not be called, "
+ "but overridden by a subclass");
}
void
-Ligature_engraver::try_stop_ligature ()
+Ligature_engraver::stop_translation_timestep ()
{
if (finished_ligature_)
{
- typeset_grob (finished_ligature_);
+ if (!finished_primitives_.size ())
+ {
+ finished_ligature_->programming_error ("Ligature_engraver::stop_translation_timestep (): "
+ "junking empty ligature");
+ }
+ else
+ {
+ typeset_ligature (finished_ligature_, finished_primitives_);
+ finished_primitives_.clear ();
+ }
finished_ligature_ = 0;
}
-}
-void
-Ligature_engraver::stop_translation_timestep ()
-{
- try_stop_ligature ();
+ reqs_drul_[START] = 0;
+ reqs_drul_[STOP] = 0;
}
void
Ligature_engraver::finalize ()
{
- try_stop_ligature ();
+ if (finished_ligature_)
+ {
+ typeset_ligature (finished_ligature_, finished_primitives_);
+ finished_primitives_.clear ();
+ finished_ligature_ = 0;
+ }
if (ligature_)
{
prev_start_req_->origin ()->warning (_ ("unterminated ligature"));
}
}
+Spanner *
+Ligature_engraver::current_ligature ()
+{
+ return ligature_;
+}
+
void
Ligature_engraver::acknowledge_grob (Grob_info info)
{
if (ligature_)
{
- if (Ligature_head::has_interface (info.grob_))
+ if (Note_head::has_interface (info.grob_))
{
- info.grob_->set_grob_property ("ligature-primitive-callback",
- brew_ligature_primitive_proc);
+ primitives_.push (info);
+ info.grob_->set_grob_property ("print-function",
+ brew_ligature_primitive_proc);
}
- else if (Rest::has_interface (info.grob_))
+ if (Rest::has_interface (info.grob_))
{
info.music_cause ()->origin ()->warning (_ ("ligature may not contain rest; ignoring rest"));
prev_start_req_->origin ()->warning (_ ("ligature was started here"));
ENTER_DESCRIPTION (Ligature_engraver,
/* descr */ "Abstract class; a concrete subclass handles Ligature_events by engraving Ligatures in a concrete style.",
-/* creats*/ "Ligature",
-/* accepts */ "ligature-event abort-event",
-/* acks */ "ligature-head-interface rest-interface",
+/* creats */ "",
+/* accepts */ "ligature-event",
+/* acks */ "note-head-interface rest-interface",
/* reads */ "",
/* write */ "");