From: hanwen Date: Sun, 19 May 2002 00:06:27 +0000 (+0000) Subject: '' X-Git-Tag: release/1.5.59~40 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=73bacd92fa83b07e3c329fbd84a70bac971bd8d3;p=lilypond.git '' --- diff --git a/input/bugs/tremolo.ly b/input/bugs/tremolo.ly new file mode 100644 index 0000000000..0b9b70755b --- /dev/null +++ b/input/bugs/tremolo.ly @@ -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 index 0000000000..44a6f2bc92 --- /dev/null +++ b/input/test/mensural-ligatures.ly @@ -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 index 0000000000..571777bde4 --- /dev/null +++ b/lily/function-documentation.cc @@ -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 index 0000000000..c502c04bd0 --- /dev/null +++ b/lily/include/ligature-head.hh @@ -0,0 +1,28 @@ +/* + ligature-head.hh -- part of GNU LilyPond + + (c) 2002 Juergen Reuter +*/ + +#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 index 0000000000..3c8ed0879f --- /dev/null +++ b/lily/include/mensural-ligature.hh @@ -0,0 +1,40 @@ +/* + mensural-ligature.hh -- part of GNU LilyPond + + source file of the GNU LilyPond music typesetter + + (c) 2002 Juergen Reuter +*/ + +#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 index 0000000000..cda7f26dde --- /dev/null +++ b/lily/ligature-engraver.cc @@ -0,0 +1,196 @@ +/* + ligature-engraver.cc -- implement Ligature_engraver + + source file of the GNU LilyPond music typesetter + + (c) 2002 Juergen Reuter + + */ +#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 (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 index 0000000000..5b6445a8d6 --- /dev/null +++ b/lily/ligature-head.cc @@ -0,0 +1,46 @@ +/* + ligature-head.cc -- implement Ligature_head + + source file of the GNU LilyPond music typesetter + + (c) 2002 Juergen Reuter +*/ + +#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::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 index 0000000000..9adb710d8f --- /dev/null +++ b/lily/mensural-ligature-engraver.cc @@ -0,0 +1,583 @@ +/* + mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver + + source file of the GNU LilyPond music typesetter + + (C) 2002 Juergen Reuter + */ + +#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 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->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 (primitives_arr_[i-2].grob_l_) : 0; + Item *last_primitive = (i > 0) ? + dynamic_cast (primitives_arr_[i-1].grob_l_) : 0; + Item *primitive = (i < primitives_arr_.size ()) ? + dynamic_cast (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 (info.grob_l_)); + Note_req *nr = dynamic_cast (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 (info1.music_cause ()); + Note_req *nr2 = dynamic_cast (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 (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 (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 (info.music_cause ()); + Pitch pitch = *unsmob_pitch (nr->get_mus_property ("pitch")); + if (i > 0) + { + Item *primitive = dynamic_cast (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 index 0000000000..3d6a4d144e --- /dev/null +++ b/lily/mensural-ligature.cc @@ -0,0 +1,258 @@ +/* + mensural-ligature.cc -- implement Mensural_ligature + + source file of the GNU LilyPond music typesetter + + (c) 2002 Juergen Reuter +*/ + +#include +#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");