From 680d569e15890605428c5174b438ac11b63c32d5 Mon Sep 17 00:00:00 2001 From: Jan Nieuwenhuizen Date: Wed, 26 Jun 2002 11:15:21 +0000 Subject: [PATCH] * input/test/ambitus.ly: * lily/ambitus-engraver.cc: * lily/ambitus.cc: * lily/include/ambitus.hh: New file. --- ChangeLog | 7 +- VERSION | 2 +- input/test/ambitus.ly | 45 ++++++++ lily/ambitus-engraver.cc | 180 +++++++++++++++++++++++++++++ lily/ambitus.cc | 236 +++++++++++++++++++++++++++++++++++++++ lily/include/ambitus.hh | 21 ++++ 6 files changed, 489 insertions(+), 2 deletions(-) create mode 100644 input/test/ambitus.ly create mode 100644 lily/ambitus-engraver.cc create mode 100644 lily/ambitus.cc create mode 100644 lily/include/ambitus.hh diff --git a/ChangeLog b/ChangeLog index a4bc93e300..d5b41c7afe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2002-06-26 Jan Nieuwenhuizen + * input/test/ambitus.ly: + * lily/ambitus-engraver.cc: + * lily/ambitus.cc: + * lily/include/ambitus.hh: New file. + * GNUmakefile.in (local-clean): Also remove builddir-setup's symlinks. 2002-06-25 Juergen Reuter @@ -7,7 +12,7 @@ * input/test/ambitus.ly, lily/ambitus-engraver.cc, lily/ambitus.cc, lily/include/ambitus.hh, ly/engraver-init.ly, scm/basic-properties.scm, scm/grob-description.scm, - scm/grob-property-description.scm: added support for ambitus + scm/grob-property-description.scm: Add support for ambitus. 2002-06-24 Han-Wen Nienhuys diff --git a/VERSION b/VERSION index 4ab904b670..cec8622582 100644 --- a/VERSION +++ b/VERSION @@ -2,7 +2,7 @@ PACKAGE_NAME=LilyPond MAJOR_VERSION=1 MINOR_VERSION=5 PATCH_LEVEL=63 -MY_PATCH_LEVEL= +MY_PATCH_LEVEL=jcn1 # Use the above to send patches: MY_PATCH_LEVEL is always empty for a # released version. diff --git a/input/test/ambitus.ly b/input/test/ambitus.ly new file mode 100644 index 0000000000..54f81651d6 --- /dev/null +++ b/input/test/ambitus.ly @@ -0,0 +1,45 @@ +\version "1.5.49" + +upper = \notes \relative c { + \clef "treble" + \key c \minor + as'' c e bes f cis d e f g f e d f d e + f d e e d f d e e d f d e e d f d e + f d e e d f d e e d f d e e d f d e +} + +lower = \notes \relative c { + \clef "treble" + \key e \major + e'2 b4 g a c es fis a cis b a g f e d + f e d e f g f e d e f g f e d e f g + f e d e f g f e d e f g f e d e f g +} + +\score { \context ChoirStaff { + < + \context Staff = one { \upper } + \context Staff = three { \lower } + > } + \paper { + \translator { + \ScoreContext + breakAlignOrder = #'( + instrument-name + left-edge + ambitus + span-bar + breathing-sign + clef + key-signature + staff-bar + time-signature + custos + ) + } + \translator { + \VoiceContext + \consists Ambitus_engraver + } + } +} diff --git a/lily/ambitus-engraver.cc b/lily/ambitus-engraver.cc new file mode 100644 index 0000000000..fb0af0286b --- /dev/null +++ b/lily/ambitus-engraver.cc @@ -0,0 +1,180 @@ +/* + ambitus-engraver.cc -- implement Ambitus_engraver + + source file of the GNU LilyPond music typesetter + + (C) 2002 Juergen Reuter +*/ + +#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 (info.grob_l_); + if (item) + { + if (Note_head::has_interface (info.grob_l_)) + { + Note_req *nr = dynamic_cast (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 */ ""); diff --git a/lily/ambitus.cc b/lily/ambitus.cc new file mode 100644 index 0000000000..ccc7ba3318 --- /dev/null +++ b/lily/ambitus.cc @@ -0,0 +1,236 @@ +/* + ambitus.cc -- implement Ambitus + + source file of the GNU LilyPond music typesetter + + (C) 2002 Juergen Reuter +*/ + +#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"); diff --git a/lily/include/ambitus.hh b/lily/include/ambitus.hh new file mode 100644 index 0000000000..c6b21388d1 --- /dev/null +++ b/lily/include/ambitus.hh @@ -0,0 +1,21 @@ +/* + ambitus.hh + + source file of the GNU LilyPond music typesetter + + (C) 2000 Juergen Reuter +*/ + +#ifndef AMBITUS_HH +#define AMBITUS_HH + +#include "lily-guile.hh" + +struct Ambitus +{ + DECLARE_SCHEME_CALLBACK (brew_molecule, (SCM smob)); + static bool has_interface (Grob*); +}; + +#endif // AMBITUS_HH + -- 2.39.5