diff -Naur lilypond-1.5.63/ChangeLog lilypond-1.5.63.NEW/ChangeLog --- lilypond-1.5.63/ChangeLog Mon Jun 24 01:26:22 2002 +++ lilypond-1.5.63.NEW/ChangeLog Tue Jun 25 22:24:35 2002 @@ -1,3 +1,10 @@ +2002-06-25 Juergen Reuter + + * 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 + 2002-06-24 Han-Wen * lily/grob-scheme.cc: new file diff -Naur lilypond-1.5.63/input/test/ambitus.ly lilypond-1.5.63.NEW/input/test/ambitus.ly --- lilypond-1.5.63/input/test/ambitus.ly Thu Jan 1 01:00:00 1970 +++ lilypond-1.5.63.NEW/input/test/ambitus.ly Tue Jun 25 22:08:20 2002 @@ -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 -Naur lilypond-1.5.63/lily/ambitus-engraver.cc lilypond-1.5.63.NEW/lily/ambitus-engraver.cc --- lilypond-1.5.63/lily/ambitus-engraver.cc Thu Jan 1 01:00:00 1970 +++ lilypond-1.5.63.NEW/lily/ambitus-engraver.cc Tue Jun 25 20:51:14 2002 @@ -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 -Naur lilypond-1.5.63/lily/ambitus.cc lilypond-1.5.63.NEW/lily/ambitus.cc --- lilypond-1.5.63/lily/ambitus.cc Thu Jan 1 01:00:00 1970 +++ lilypond-1.5.63.NEW/lily/ambitus.cc Tue Jun 25 20:33:08 2002 @@ -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 -Naur lilypond-1.5.63/lily/include/ambitus.hh lilypond-1.5.63.NEW/lily/include/ambitus.hh --- lilypond-1.5.63/lily/include/ambitus.hh Thu Jan 1 01:00:00 1970 +++ lilypond-1.5.63.NEW/lily/include/ambitus.hh Wed Jun 5 03:24:52 2002 @@ -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 + diff -Naur lilypond-1.5.63/ly/engraver-init.ly lilypond-1.5.63.NEW/ly/engraver-init.ly --- lilypond-1.5.63/ly/engraver-init.ly Mon Jun 24 01:26:23 2002 +++ lilypond-1.5.63.NEW/ly/engraver-init.ly Tue Jun 25 20:45:07 2002 @@ -414,6 +414,7 @@ breakAlignOrder = #'( instrument-name left-edge + ambitus span-bar breathing-sign clef diff -Naur lilypond-1.5.63/scm/basic-properties.scm lilypond-1.5.63.NEW/scm/basic-properties.scm --- lilypond-1.5.63/scm/basic-properties.scm Mon Jun 17 10:02:33 2002 +++ lilypond-1.5.63.NEW/scm/basic-properties.scm Tue Jun 25 20:34:48 2002 @@ -62,6 +62,7 @@ '( ((none Instrument_name) . (extra-space 1.0)) ((none Left_edge_item) . (extra-space 0.0)) + ((none Ambitus) . (extra-space 1.0)) ((none Clef_item) . (minimum-space-pair 1.0)) ((none Staff_bar) . (minimum-space-pair 0.0)) ((none Clef_item) . (minimum-space-pair 1.0)) diff -Naur lilypond-1.5.63/scm/grob-description.scm lilypond-1.5.63.NEW/scm/grob-description.scm --- lilypond-1.5.63/scm/grob-description.scm Mon Jun 24 01:26:23 2002 +++ lilypond-1.5.63.NEW/scm/grob-description.scm Tue Jun 25 20:39:54 2002 @@ -28,6 +28,25 @@ (meta . ((interfaces . (accidental-placement-interface)))) )) + (Ambitus + . ( + (breakable . #t) + (break-align-symbol . ambitus) + (molecule-callback . ,Ambitus::brew_molecule) + (font-family . music) + (note-head-style . "noteheads-2") + (visibility-lambda . ,begin-of-line-visible) + (join-heads . #t) + (space-alist . ( + (clef . (extra-space . 0.0)) + (key-signature . (extra-space . 0.0)) + (staff-bar . (extra-space . 0.0)) + (time-signature . (extra-space . 0.0)) + (first-note . (extra-space . 0.0)) + )) + (meta . ((interfaces . (ambitus-interface staff-symbol-referencer-interface break-aligned-interface font-interface)))) + )) + (Arpeggio . ( (X-extent-callback . ,Arpeggio::width_callback) @@ -155,6 +174,7 @@ (break-align-symbol . breathing-sign) (breakable . #t) (space-alist . ( + (ambitus . (extra-space . 2.0)) (key-signature . (minimum-space . 1.5)) (staff-bar . (minimum-space . 1.5)) (clef . (minimum-space . 2.0)) @@ -178,6 +198,7 @@ (break-align-symbol . clef) (break-visibility . ,begin-of-line-visible) (space-alist . ( + (ambitus . (extra-space . 2.0)) (staff-bar . (minimum-space . 3.7)) (key-signature . (minimum-space . 4.0)) (time-signature . (minimum-space . 4.2)) @@ -282,6 +303,7 @@ (X-extent-callback . ,Grob::point_dimension_callback) (breakable . #t) (space-alist . ( + (ambitus . (extra-space . 2.0)) (time-signature . (extra-space . 0.0)) (staff-bar . (extra-space . 0.0)) (breathing-sign . (minimum-space . 0.0)) diff -Naur lilypond-1.5.63/scm/grob-property-description.scm lilypond-1.5.63.NEW/scm/grob-property-description.scm --- lilypond-1.5.63/scm/grob-property-description.scm Mon Jun 24 01:26:23 2002 +++ lilypond-1.5.63.NEW/scm/grob-property-description.scm Tue Jun 25 20:44:02 2002 @@ -191,6 +191,7 @@ (grob-property-description 'interfaces list? "list of symbols indicating the interfaces supported by this object. Is initialized from the @code{meta} field.") (grob-property-description 'inversion list? " musical-pitch, optional.") (grob-property-description 'items-worth-living grob-list? "list of interesting items. If empty in a particular system, clear that system.") +(grob-property-description 'join-heads boolean? "Whether to join the noteheads of an ambitus grob with a vertical line.") (grob-property-description 'kern number? "amount of extra white space to add. For text, this is `relative'(?) to the current alignment. @@ -262,6 +263,7 @@ (grob-property-description 'no-spacing-rods boolean? "read from grobs: boolean that makes Separation_item ignore this item (MOVE ME TO ITEM).") (grob-property-description 'no-stem-extend boolean? "should stem not be extended to middle staff line?.") (grob-property-description 'non-default boolean? "not set because of existence of a bar?.") +(grob-property-description 'note-head-style string? "name of the font character to be used as note heads in the ambitus grob.") (grob-property-description 'note-heads grob-list? "List of note head grobs") (grob-property-description 'number-threshold number? "only put numbers bigger than this threshold over multi measure rest.") (grob-property-description 'old-accidentals list? "list of (pitch, accidental) pairs.")