--- /dev/null
+/*
+ ambitus-engraver.cc -- implement Ambitus_engraver
+
+ source file of the GNU LilyPond music typesetter
+
+ (C) 2002 Juergen Reuter <reuter@ipd.uka.de>
+*/
+
+#include "engraver.hh"
+#include "item.hh"
+#include "note-head.hh"
+#include "staff-symbol-referencer.hh"
+#include "musical-request.hh"
+#include "pitch.hh"
+
+/*
+ * This class implements an engraver for ambitus grobs.
+ *
+ * TODO: There are quite some conceptional issues left open:
+ *
+ * - Many publishers put ambitus _before_ the first occurrence of a
+ * clef. Hence, formally the pitches are undefined in this case. Of
+ * course, one could always silently assume that ambitus pitches refer
+ * to the first occurrence of a clef. Or should we, by default, put
+ * the ambitus always after the first clef, if any?
+ *
+ * - Enharmonically equal pitches: Assume piece contains once a "gis",
+ * another time an "aes" as highest pitch. Which one should be
+ * selected for the ambitus grob? The "aes", because it is
+ * musically/notationally "higher" than "gis"? Or "gis", because (if
+ * using pure temperament) it has a slightly higher frequency? Or
+ * that pitch that come closer to the key signature? But there may be
+ * key signature changes in the piece...
+ *
+ * - Multiple voices in single staff: Assume a vocal piece of music,
+ * where the soprano voice and the alto voice are put into the same
+ * staff (this is generally a bad idea, but unfortunately common
+ * practice). Then, there probably should be two ambitus grobs, one
+ * for each voice. But how can you see which ambitus grob refers to
+ * which voice? Most probably you can guess it from the fact that the
+ * ambitus of the alto voice typically lies in a lower range than that
+ * of the soprano voice, but this is just a heuristic rather than a
+ * generally valid rule. In the case of only two voices, using stems
+ * in the ambitus grob might help, but probably looks quite ugly.
+ *
+ * - If a piece consists of several loosely coupled sections, should
+ * there be multiple ambitus grobs allowed, one for each section?
+ * Then there probably should be some "\ambitus" request added to
+ * mudela, stating where an ambitus grob should be placed. This
+ * ambitus grob should then represent the ambitus in the range of time
+ * between this "\ambitus" request and the next one (or the end of the
+ * piece, if there is no more such request). To be compliant with the
+ * current implementation, we might implicitly assume an "\ambitus"
+ * request at the beginning of the piece, but then the question where
+ * to put this first ambitus grob (before/after the clef?) becomes
+ * even more urgent.
+ *
+ * - Incipits of transcribed music may need special treatment for
+ * ambitus, since, for readability, the ambitus most probably should
+ * not refer to the ancient clefs of the incipit, but rather to the
+ * clefs used in the transcribed parts.
+ */
+class Ambitus_engraver : public Engraver
+{
+public:
+TRANSLATOR_DECLARATIONS(Ambitus_engraver);
+ virtual void start_translation_timestep ();
+ virtual void acknowledge_grob (Grob_info);
+ virtual void create_grobs ();
+ virtual void stop_translation_timestep ();
+
+private:
+ void create_ambitus ();
+ Item *ambitus_p_;
+ int isActive;
+ Pitch pitch_min, pitch_max;
+};
+
+Ambitus_engraver::Ambitus_engraver ()
+{
+ ambitus_p_ = 0; isActive = 0;
+
+ // (pitch_min > pitch_max) means that pitches are not yet
+ // initialized
+ pitch_min = Pitch (0, 0, +1);
+ pitch_max = Pitch (0, 0, -1);
+}
+
+void
+Ambitus_engraver::stop_translation_timestep ()
+{
+ if (!ambitus_p_) {
+ create_ambitus ();
+ }
+ if (ambitus_p_ && isActive)
+ {
+ SCM key_signature = get_property ("keySignature");
+ ambitus_p_->set_grob_property ("keySignature", key_signature);
+ typeset_grob (ambitus_p_);
+ //ambitus_p_ = 0;
+ isActive = 0;
+ }
+}
+
+void
+Ambitus_engraver::start_translation_timestep ()
+{
+ if (!ambitus_p_) {
+ create_ambitus ();
+ }
+}
+
+void
+Ambitus_engraver::create_grobs ()
+{
+ if (!ambitus_p_) {
+ create_ambitus ();
+ }
+}
+
+void
+Ambitus_engraver::acknowledge_grob (Grob_info info)
+{
+ if (!ambitus_p_) {
+ create_ambitus ();
+ }
+ if (!ambitus_p_)
+ return;
+ Item *item = dynamic_cast <Item *>(info.grob_l_);
+ if (item)
+ {
+ if (Note_head::has_interface (info.grob_l_))
+ {
+ Note_req *nr = dynamic_cast<Note_req*> (info.music_cause ());
+ if (nr)
+ {
+ Pitch pitch = *unsmob_pitch (nr->get_mus_property ("pitch"));
+ if (Pitch::compare (pitch_min, pitch_max) > 0) // already init'd?
+ {
+ // not yet init'd; use current pitch to init min/max
+ pitch_min = pitch;
+ pitch_max = pitch;
+ ambitus_p_->set_grob_property ("pitch-min",
+ pitch_min.smobbed_copy ());
+ ambitus_p_->set_grob_property ("pitch-max",
+ pitch_max.smobbed_copy ());
+ }
+ else if (Pitch::compare (pitch, pitch_max) > 0) // new max?
+ {
+ pitch_max = pitch;
+ ambitus_p_->set_grob_property ("pitch-max",
+ pitch_max.smobbed_copy ());
+ }
+ else if (Pitch::compare (pitch, pitch_min) < 0) // new min?
+ {
+ pitch_min = pitch;
+ ambitus_p_->set_grob_property ("pitch-min",
+ pitch_min.smobbed_copy ());
+ }
+ }
+ }
+ }
+}
+
+void
+Ambitus_engraver::create_ambitus ()
+{
+ SCM basicProperties = get_property ("Ambitus");
+ SCM c0 = get_property ("centralCPosition");
+ ambitus_p_ = new Item (basicProperties); isActive = 1;
+ ambitus_p_->set_grob_property ("centralCPosition", c0);
+ announce_grob (ambitus_p_, SCM_EOL);
+}
+
+ENTER_DESCRIPTION(Ambitus_engraver,
+/* descr */ "",
+/* creats*/ "Ambitus",
+/* acks */ "note-head-interface",
+/* reads */ "",
+/* write */ "");
--- /dev/null
+/*
+ ambitus.cc -- implement Ambitus
+
+ source file of the GNU LilyPond music typesetter
+
+ (C) 2002 Juergen Reuter <reuter@ipd.uka.de>
+*/
+
+#include "staff-symbol-referencer.hh"
+#include "pitch.hh"
+#include "ambitus.hh"
+#include "molecule.hh"
+#include "note-head.hh"
+#include "item.hh"
+#include "font-interface.hh"
+#include "paper-def.hh"
+#include "lookup.hh"
+
+/*
+ * TODO: note-head collision handling
+ *
+ * TODO: accidentals collision handling
+ *
+ * TODO: alternative representation: adding the ambitus as text script
+ * to the instrument name (e.g. "Soprano (c^1 - f^2)").
+ *
+ * FIXME: Accidentals are too close at the note heads (it seems that
+ * the extent of the ledger lines is ignored).
+ *
+ * TODO: If (depending on breakAlignOrder) ambitus is put behind
+ * key-signature, then do not repeat accidentals that already appear
+ * in the key signature.
+ *
+ * FIXME: A staff containing more than a single context will result in
+ * multiple ambitus grobs per staff. This is basically ok, but there is
+ * currently no proper collision handling for this case.
+ *
+ * TODO: make ignore_octave and force_accidental of function
+ * number_accidentals accessible via grob properties.
+ */
+
+/**
+ * Given a pitch and a key_signature, decide what accidentals to show.
+ *
+ * Possible return values:
+ *
+ * 0: do not show any accidental
+ * 1: show pitch->alteration_i_ only
+ * 2: show pitch->alteration_i_, preceded by a natural sign
+ */
+static int
+number_accidentals (SCM key_signature, Pitch *pitch,
+ bool ignore_octave_b, bool force_accidental)
+{
+ int notename = pitch->notename_i_;
+ int octave = pitch->octave_i_;
+ int alteration = pitch->alteration_i_;
+
+ if (force_accidental) // ignore key signature
+ return 1;
+
+#if DEBUG_AMBITUS
+ scm_display (key_signature, scm_current_output_port ());
+#endif
+
+ SCM prev;
+ if (ignore_octave_b)
+ prev = ly_assoc_cdr (gh_int2scm (notename), key_signature);
+ else
+ prev = gh_assoc (gh_cons (gh_int2scm (octave), gh_int2scm (notename)),
+ key_signature);
+
+ /* should really be true unless prev == SCM_BOOL_F */
+ if (gh_pair_p (prev) && gh_pair_p (ly_cdr (prev)))
+ {
+ prev = gh_cons (ly_car (prev), ly_cadr (prev));
+ }
+
+ /* If an accidental was not found */
+ if (prev == SCM_BOOL_F)
+ prev = gh_assoc (gh_int2scm (notename), key_signature);
+
+ SCM prev_acc = (prev == SCM_BOOL_F) ? gh_int2scm (0) : ly_cdr (prev);
+ int sig_alteration = gh_number_p (prev_acc) ? gh_scm2int (prev_acc) : 0;
+
+ if (alteration == sig_alteration) // no accidental at all needed
+ return 0;
+
+ if ((alteration == 0) && (sig_alteration != 0)) // need ordinary natural
+ return 2;
+
+ if (sig_alteration == 0) // use pitch's alteration
+ return 1;
+
+ return 2;
+}
+
+void
+add_accidentals (Item *me, Molecule *head, int num_acc,
+ Pitch *pitch, String accidentals_style, Real yoffs)
+{
+ if (!num_acc)
+ return;
+ if (pitch->alteration_i_)
+ {
+ Molecule accidental (Font_interface::get_default_font (me)->
+ find_by_name (String ("accidentals-") +
+ accidentals_style +
+ to_str (pitch->alteration_i_)));
+ accidental.translate_axis (yoffs, Y_AXIS);
+ head->add_at_edge (X_AXIS, LEFT, accidental, 0.1);
+ }
+ if (num_acc == 2)
+ {
+ Molecule natural (Font_interface::get_default_font (me)->
+ find_by_name (String ("accidentals-") +
+ accidentals_style +
+ to_str ("0")));
+ natural.translate_axis (yoffs, Y_AXIS);
+ head->add_at_edge (X_AXIS, LEFT, natural, 0.1);
+ }
+}
+
+MAKE_SCHEME_CALLBACK (Ambitus,brew_molecule,1);
+SCM
+Ambitus::brew_molecule (SCM smob)
+{
+ Item *me = (Item *)unsmob_grob (smob);
+ Molecule molecule = Molecule ();
+
+ SCM scm_note_head_style = me->get_grob_property ("note-head-style");
+ String note_head_style;
+ if (gh_symbol_p (scm_note_head_style))
+ {
+ String note_head_style =
+ ly_scm2string (scm_symbol_to_string (scm_note_head_style));
+ }
+ else
+ {
+ note_head_style = String ("noteheads-2");
+ }
+ if (Font_interface::get_default_font (me)->find_by_name (note_head_style).empty_b ())
+ {
+ String message = "Ambitus: no such note head: `" + note_head_style + "'";
+ me->warning (_ (message.ch_C ()));
+ return SCM_EOL;
+ }
+
+ Pitch *pitch_min = unsmob_pitch (me->get_grob_property ("pitch-min"));
+ int p_min = pitch_min->steps ();
+ Pitch *pitch_max = unsmob_pitch (me->get_grob_property ("pitch-max"));
+ int p_max = pitch_max->steps ();
+ if (p_min > p_max)
+ {
+ String message = "Ambitus: no range to output";
+ me->warning (_ (message.ch_C ()));
+ return SCM_EOL;
+ }
+
+ SCM c0 = me->get_grob_property ("centralCPosition");
+ if (gh_number_p (c0))
+ {
+ p_min += gh_scm2int (c0);
+ p_max += gh_scm2int (c0);
+ }
+
+ // create heads
+ Molecule head_min =
+ Font_interface::get_default_font (me)->find_by_name (note_head_style);
+ head_min.translate_axis (0.5*p_min, Y_AXIS);
+ Molecule head_max =
+ Font_interface::get_default_font (me)->find_by_name (note_head_style);
+ head_max.translate_axis (0.5*p_max, Y_AXIS);
+
+ // join heads
+ if (to_boolean (me->get_grob_property ("join-heads")) &&
+ ((p_max - p_min) >= 3))
+ {
+ Real linethickness = me->paper_l ()->get_var ("linethickness");
+ Real blotdiameter = me->paper_l ()->get_var ("blotdiameter");
+ Interval x_extent = 0.5 * Interval (-linethickness, +linethickness);
+ Interval y_extent = 0.5 * Interval (p_min + 1.35, p_max - 1.35);
+ Box line_box (x_extent, y_extent);
+ Molecule line = Lookup::roundfilledbox (line_box, blotdiameter);
+ line.translate_axis (0.5 * head_min.extent (X_AXIS).length (), X_AXIS);
+ molecule.add_molecule (line);
+ }
+
+ // add ledger lines
+ Interval hd = head_min.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;
+ int interspaces = Staff_symbol_referencer::line_count (me) - 1;
+ ledger_lines =
+ Note_head::brew_ledger_lines (me, p_min, interspaces, l_extents, true);
+ ledger_lines.translate_axis (0.5 * p_min, Y_AXIS);
+ molecule.add_molecule (ledger_lines);
+ ledger_lines =
+ Note_head::brew_ledger_lines (me, p_max, interspaces, l_extents, true);
+ ledger_lines.translate_axis (0.5 * p_max, Y_AXIS);
+ molecule.add_molecule (ledger_lines);
+
+ // add accidentals
+ SCM key_signature = me->get_grob_property ("keySignature");
+ SCM scm_accidentals_style = me->get_grob_property ("accidentals-style");
+ String accidentals_style;
+ if (gh_symbol_p (scm_accidentals_style))
+ {
+ accidentals_style =
+ ly_scm2string (scm_symbol_to_string (scm_accidentals_style));
+ }
+ else
+ {
+ accidentals_style = String ("");
+ }
+ int num_acc;
+ num_acc = number_accidentals (key_signature, pitch_min, true, false);
+ add_accidentals (me, &head_min, num_acc, pitch_min,
+ accidentals_style, 0.5 * p_min);
+ num_acc = number_accidentals (key_signature, pitch_max, true, false);
+ add_accidentals (me, &head_max, num_acc, pitch_max,
+ accidentals_style, 0.5 * p_max);
+
+ // add heads
+ molecule.add_molecule (head_min);
+ molecule.add_molecule (head_max);
+
+ return molecule.smobbed_copy ();
+}
+
+ADD_INTERFACE (Ambitus, "ambitus-interface",
+ "An ambitus represents the pitch range of a voice.",
+ "note-head-style join-heads");