X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fcompletion-note-heads-engraver.cc;h=ef3a5012a26cbc5a77d192904ffb216d5082c4ab;hb=08d00bc3c27d182e2c59ccde7898a43fe03f81e4;hp=85609c295a798272175a488d70ead6c1a88b4761;hpb=736bfdaea194aade5d20d9f749f009c96d41b953;p=lilypond.git diff --git a/lily/completion-note-heads-engraver.cc b/lily/completion-note-heads-engraver.cc index 85609c295a..ef3a5012a2 100644 --- a/lily/completion-note-heads-engraver.cc +++ b/lily/completion-note-heads-engraver.cc @@ -1,306 +1,336 @@ /* - head-grav.cc -- part of GNU LilyPond + This file is part of LilyPond, the GNU music typesetter. - (c) 1997--2001 Han-Wen Nienhuys + Copyright (C) 1997--2015 Han-Wen Nienhuys + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . */ -#include +#include +using namespace std; -#include "rhythmic-head.hh" -#include "paper-def.hh" -#include "musical-request.hh" -#include "dots.hh" #include "dot-column.hh" -#include "staff-symbol-referencer.hh" +#include "dots.hh" +#include "duration.hh" +#include "global-context.hh" #include "item.hh" +#include "output-def.hh" +#include "pitch.hh" +#include "rhythmic-head.hh" #include "score-engraver.hh" +#include "spanner.hh" +#include "staff-symbol-referencer.hh" +#include "stream-event.hh" +#include "tie.hh" +#include "tie-column.hh" #include "warn.hh" +#include "misc.hh" + +#include "translator.icc" + +/* + How does this work? + + When we catch the note, we predict the end of the note. We keep the + events living until we reach the predicted end-time. + + Every time process_music () is called and there are note events, we + figure out how long the note to typeset should be. It should be no + longer than what's specified, than what is left to do and it should + not cross barlines or sub-bar units. + + We copy the events into scratch note events, to make sure that we get + all durations exactly right. +*/ -/** - make balls and rests - */ class Completion_heads_engraver : public Engraver { - Link_array note_p_arr_; - - Link_array dot_p_arr_; - Link_array note_req_l_arr_; - Link_array scratch_note_reqs_; - + vector notes_; + vector prev_notes_; + // Must remember notes for explicit ties. + vector ties_; + vector note_events_; + Spanner *tie_column_; Moment note_end_mom_; - bool first_b_; + bool is_first_; Rational left_to_do_; + Rational do_nothing_until_; + Rational factor_; + + Moment next_moment (Rational const &); + Item *make_note_head (Stream_event *); - Moment next_barline_moment (); - Duration find_nearest_duration (Rational length); - public: - TRANSLATOR_DECLARATIONS(Completion_heads_engraver); + TRANSLATOR_DECLARATIONS (Completion_heads_engraver); protected: virtual void initialize (); - virtual void start_translation_timestep (); - virtual bool try_music (Music *req_l) ; - virtual void process_music (); - virtual void stop_translation_timestep (); + void make_tie (Grob *, Grob *); + void start_translation_timestep (); + void process_music (); + void stop_translation_timestep (); + void listen_note (Stream_event *); }; void Completion_heads_engraver::initialize () { - first_b_ = false; + is_first_ = false; } -bool -Completion_heads_engraver::try_music (Music *m) +void +Completion_heads_engraver::listen_note (Stream_event *ev) { - if (Note_req * n =dynamic_cast (m)) + note_events_.push_back (ev); + + is_first_ = true; + Moment now = now_mom (); + Moment musiclen = get_event_length (ev, now); + + note_end_mom_ = max (note_end_mom_, (now + musiclen)); + do_nothing_until_ = Rational (0, 0); +} + +/* + The duration _until_ the next bar line or completion unit +*/ +Moment +Completion_heads_engraver::next_moment (Rational const ¬e_len) +{ + Moment *e = unsmob (get_property ("measurePosition")); + Moment *l = unsmob (get_property ("measureLength")); + if (!e || !l || !to_boolean (get_property ("timing"))) { - note_req_l_arr_.push (n); - - first_b_ = true; - Moment musiclen = m->length_mom (); - Moment now = now_mom(); - - if (now_mom ().grace_part_) - { - musiclen.grace_part_ = musiclen.main_part_ ; - musiclen.main_part_ = Rational (0,1); - } - note_end_mom_ = note_end_mom_ >? (now + musiclen); - return true; + return Moment (0, 0); } - else if (dynamic_cast (m)) + + Moment result = *l - *e; + if (result < 0) { - return now_mom () < note_end_mom_; + programming_error ("invalid measure position: " + + e->to_string () + " of " + l->to_string ()); + return 0; } - return false; - -} + Moment const *unit = unsmob (get_property ("completionUnit")); -Moment -Completion_heads_engraver::next_barline_moment ( ) -{ - Moment *e = unsmob_moment (get_property ("measurePosition")); - Moment *l = unsmob_moment (get_property ("measureLength")); - if (!e || !l) + if (unit) { - programming_error ("No timing props set?"); - return Moment (1,1); + Rational const now_unit = e->main_part_ / unit->main_part_; + if (now_unit.den () > 1) + { + /* + within a unit - go to the end of that + */ + result = unit->main_part_ + * (Rational (1) - (now_unit - now_unit.trunc_rat ())); + } + else + { + /* + at the beginning of a unit: + take a power-of-two number of units, but not more than required, + since then the Duration constructor destroys the unit structure + */ + if (note_len < result.main_part_) + result.main_part_ = note_len; + Rational const step_unit = result.main_part_ / unit->main_part_; + if (step_unit.den () < step_unit.num ()) + { + int const log2 + = intlog2 (int (step_unit.num () / step_unit.den ())); + result.main_part_ = unit->main_part_ * Rational (1 << log2); + } + } } - return (*l - *e); + return result; } -Duration -Completion_heads_engraver::find_nearest_duration (Rational length) +Item * +Completion_heads_engraver::make_note_head (Stream_event *ev) { - int log_limit= 6; - - Duration d(0,0); + Item *note = make_item ("NoteHead", ev->self_scm ()); + Pitch *pit = unsmob (ev->get_property ("pitch")); - /* - this could surely be done more efficient. Left to the reader as an - excercise. */ - while (d.length_mom () > length && d.duration_log () < log_limit) - { - if (d.dot_count ()) - { - d = Duration (d.duration_log (), d.dot_count ()- 1); - continue; - } - else - { - d = Duration (d.duration_log () + 1, 2); - } - } + int pos = pit ? pit->steps () : 0; + SCM c0 = get_property ("middleCPosition"); + if (scm_is_number (c0)) + pos += scm_to_int (c0); - if (d.duration_log () >= log_limit) - { - // junk the dots. - d = Duration (d.duration_log (), 0); + note->set_property ("staff-position", scm_from_int (pos)); - // scale up. - d = d.compressed (length / d.length_mom ()); - } - - return d; + return note; } void Completion_heads_engraver::process_music () { - if (!first_b_ && !left_to_do_) - return ; + if (!is_first_ && !left_to_do_) + return; + + is_first_ = false; + + Moment now = now_mom (); + if (do_nothing_until_ > now.main_part_) + return; - first_b_ = false; - Duration note_dur; Duration *orig = 0; if (left_to_do_) { - note_dur = find_nearest_duration (left_to_do_); + /* + note that note_dur may be strictly less than left_to_do_ + (say, if left_to_do_ == 5/8) + */ + note_dur = Duration (left_to_do_ / factor_, false).compressed (factor_); } else { - orig = unsmob_duration (note_req_l_arr_[0]->get_mus_property ("duration")); + orig = unsmob (note_events_[0]->get_property ("duration")); note_dur = *orig; + SCM factor = get_property ("completionFactor"); + if (ly_is_procedure (factor)) + factor = scm_call_2 (factor, + context ()->self_scm (), + note_dur.smobbed_copy ()); + factor_ = robust_scm2rational (factor, note_dur.factor ()); + left_to_do_ = orig->get_length (); } - - Moment nb = next_barline_moment (); - if (nb < note_dur.length_mom ()) + Moment nb = next_moment (note_dur.get_length ()); + if (nb.main_part_ && nb < note_dur.get_length ()) { - note_dur = find_nearest_duration (nb.main_part_); - - Moment next = now_mom(); - next.main_part_ += note_dur.length_mom (); - top_engraver ()->add_moment_to_process (next); + note_dur = Duration (nb.main_part_ / factor_, false).compressed (factor_); } - if (orig) - { - left_to_do_ = orig->length_mom (); - } + do_nothing_until_ = now.main_part_ + note_dur.get_length (); - if (orig && note_dur.length_mom() != orig->length_mom()) + for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++) { - if (!scratch_note_reqs_.size ()) - for (int i = 0; i < note_req_l_arr_.size (); i++) - { - Music * m = note_req_l_arr_[i]->clone (); - scratch_note_reqs_.push (m); - } - - for (int i =0; i < scratch_note_reqs_.size (); i++) - scratch_note_reqs_[i]->set_mus_property ("duration", note_dur.smobbed_copy ()); + bool need_clone = !orig || *orig != note_dur; + Stream_event *event = note_events_[i]; + + if (need_clone) + event = event->clone (); + + SCM pits = note_events_[i]->get_property ("pitch"); + event->set_property ("pitch", pits); + event->set_property ("duration", note_dur.smobbed_copy ()); + event->set_property ("length", Moment (note_dur.get_length ()).smobbed_copy ()); + event->set_property ("duration-log", scm_from_int (note_dur.duration_log ())); + + /* + The Completion_heads_engraver splits an event into a group of consecutive events. + For each event in the group, the property "autosplit-end" denotes whether the current event + was truncated during splitting. Based on "autosplit-end", the Tie_engraver decides whether a + tie event should be processed. + */ + event->set_property ("autosplit-end", + ly_bool2scm (left_to_do_ - note_dur.get_length () > Rational (0))); + + Item *note = make_note_head (event); + if (need_clone) + event->unprotect (); + notes_.push_back (note); } - - for (int i = 0; - left_to_do_ && i < note_req_l_arr_.size (); i++) + if (prev_notes_.size () == notes_.size ()) { - Item *note_p = new Item (get_property ("NoteHead")); - - Staff_symbol_referencer::set_interface (note_p); - - Music * req = note_req_l_arr_[i]; - if (scratch_note_reqs_.size()) - { - req = scratch_note_reqs_[i]; - req->set_mus_property ("pitch", - note_req_l_arr_[i]->get_mus_property ("pitch")); - } - note_p->set_grob_property ("duration-log", - gh_int2scm (note_dur.duration_log ())); - - int dots= note_dur.dot_count (); - if (dots) - { - Item * d = new Item (get_property ("Dots")); - Rhythmic_head::set_dots (note_p, d); - - /* - measly attempt to save an eeny-weenie bit of memory. - */ - if (dots != gh_scm2int (d->get_grob_property ("dot-count"))) - d->set_grob_property ("dot-count", gh_int2scm (dots)); - - d->set_parent (note_p, Y_AXIS); - announce_grob (d,0); - dot_p_arr_.push (d); - } - - Pitch *pit =unsmob_pitch (req->get_mus_property ("pitch")); - - int pos = pit->steps (); - SCM c0 = get_property ("centralCPosition"); - if (gh_number_p (c0)) - pos += gh_scm2int (c0); - - note_p->set_grob_property ("staff-position", gh_int2scm (pos)); - if (to_boolean (get_property ("easyPlay"))) - { - char s[2] = "a"; - s[0] = (pit->notename_i_ + 2)%7 + 'a'; - - s[0] = toupper (s[0]); - note_p->set_grob_property ("note-character", ly_str02scm (s)); - } - - announce_grob (note_p,req); - note_p_arr_.push (note_p); + for (vsize i = 0; i < notes_.size (); i++) + make_tie (prev_notes_[i], notes_[i]); } - left_to_do_ -= note_dur.length_mom (); + if (ties_.size () && !tie_column_) + tie_column_ = make_spanner ("TieColumn", ties_[0]->self_scm ()); + if (tie_column_) + for (vsize i = ties_.size (); i--;) + Tie_column::add_tie (tie_column_, ties_[i]); + left_to_do_ -= note_dur.get_length (); + if (left_to_do_) + get_global_context ()->add_moment_to_process (now.main_part_ + note_dur.get_length ()); /* don't do complicated arithmetic with grace notes. - */ - if (orig - && now_mom().grace_part_ ) - { - left_to_do_ = Rational (0,0); - } - + */ + if (orig && now_mom ().grace_part_) + left_to_do_ = Rational (0, 0); +} + +void +Completion_heads_engraver::make_tie (Grob *left, Grob *right) +{ + Spanner *p = make_spanner ("Tie", SCM_EOL); + Tie::set_head (p, LEFT, left); + Tie::set_head (p, RIGHT, right); + announce_end_grob (p, SCM_EOL); + ties_.push_back (p); } - + void Completion_heads_engraver::stop_translation_timestep () { - for (int i=0; i < note_p_arr_.size (); i++) - { - typeset_grob (note_p_arr_[i]); - } - note_p_arr_.clear (); - - for (int i=0; i < dot_p_arr_.size (); i++) - { - typeset_grob (dot_p_arr_[i]); - } - dot_p_arr_.clear (); + ties_.clear (); + tie_column_ = 0; - for (int i = scratch_note_reqs_.size(); i--;) - { - scm_gc_unprotect_object (scratch_note_reqs_[i]->self_scm () ); - - } - scratch_note_reqs_.clear(); + if (notes_.size ()) + prev_notes_ = notes_; + notes_.clear (); } -Tie_req * tie_req = 0; - void Completion_heads_engraver::start_translation_timestep () { Moment now = now_mom (); if (note_end_mom_.main_part_ <= now.main_part_) { - note_req_l_arr_.clear (); + note_events_.clear (); + prev_notes_.clear (); } + context ()->set_property ("completionBusy", + ly_bool2scm (note_events_.size ())); +} - if (left_to_do_) - { - if (!tie_req) - tie_req = new Tie_req; - - bool succ = daddy_trans_l_->try_music (tie_req); - if (!succ) - { - programming_error ("Completion_heads_engraver: no-one to make tie."); - } - } +Completion_heads_engraver::Completion_heads_engraver () +{ + tie_column_ = 0; } -Completion_heads_engraver::Completion_heads_engraver() +void +Completion_heads_engraver::boot () { + ADD_LISTENER (Completion_heads_engraver, note); } -ENTER_DESCRIPTION(Completion_heads_engraver, -/* descr */ "This engraver replaces -@code{Note_heads_engraver}. It plays some trickery to -break long notes and automatically tie them into the next measure.", -/* creats*/ "NoteHead Dots", -/* acks */ "", -/* reads */ "easyPlay centralCPosition measurePosition measureLength", -/* write */ ""); +ADD_TRANSLATOR (Completion_heads_engraver, + /* doc */ + "This engraver replaces @code{Note_heads_engraver}. It plays" + " some trickery to break long notes and automatically tie them" + " into the next measure.", + + /* create */ + "NoteHead " + "Tie " + "TieColumn ", + + /* read */ + "completionFactor " + "completionUnit " + "measureLength " + "measurePosition " + "middleCPosition " + "timing ", + + /* write */ + "completionBusy " + );