summaryrefslogtreecommitdiffstats
path: root/src/gui/editors/notation/NotationGroup.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/editors/notation/NotationGroup.cpp')
-rw-r--r--src/gui/editors/notation/NotationGroup.cpp979
1 files changed, 979 insertions, 0 deletions
diff --git a/src/gui/editors/notation/NotationGroup.cpp b/src/gui/editors/notation/NotationGroup.cpp
new file mode 100644
index 0000000..78525d9
--- /dev/null
+++ b/src/gui/editors/notation/NotationGroup.cpp
@@ -0,0 +1,979 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program 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 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationGroup.h"
+#include "misc/Debug.h"
+
+#include "base/Equation.h"
+#include "base/Event.h"
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "base/Quantizer.h"
+#include "NotationChord.h"
+#include "NotationElement.h"
+#include "NotationProperties.h"
+#include "NotationStaff.h"
+#include "NoteStyleFactory.h"
+#include "NotePixmapFactory.h"
+
+
+namespace Rosegarden
+{
+
+NotationGroup::NotationGroup(NotationElementList &nel,
+ NELIterator i, const Quantizer *q,
+ std::pair<timeT, timeT> barRange,
+ const NotationProperties &p,
+ const Clef &clef, const Key &key) :
+ AbstractSet<NotationElement, NotationElementList>(nel, i, q),
+ m_barRange(barRange),
+ //!!! What if the clef and/or key change in the course of the group?
+ m_clef(clef),
+ m_key(key),
+ m_weightAbove(0),
+ m_weightBelow(0),
+ m_userSamples(false),
+ m_type(Beamed),
+ m_properties(p)
+{
+ if (!(*i)->event()->get<Int>
+ (BaseProperties::BEAMED_GROUP_ID, m_groupNo)) m_groupNo = -1;
+
+ initialise();
+
+ /*
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: id is " << m_groupNo << endl;
+ i = getInitialElement();
+ while (i != getContainer().end()) {
+ long gid = -1;
+ (*i)->event()->get<Int>(BEAMED_GROUP_ID, gid);
+ NOTATION_DEBUG << "Found element with group id "
+ << gid << endl;
+ if (i == getFinalElement()) break;
+ ++i;
+ }
+ */
+}
+
+NotationGroup::NotationGroup(NotationElementList &nel,
+ const Quantizer *q,
+ const NotationProperties &p,
+ const Clef &clef, const Key &key) :
+ AbstractSet<NotationElement, NotationElementList>(nel, nel.end(), q),
+ m_barRange(0, 0),
+ //!!! What if the clef and/or key change in the course of the group?
+ m_clef(clef),
+ m_key(key),
+ m_weightAbove(0),
+ m_weightBelow(0),
+ m_userSamples(true),
+ m_groupNo( -1),
+ m_type(Beamed),
+ m_properties(p)
+{
+ //...
+}
+
+NotationGroup::~NotationGroup()
+{}
+
+bool NotationGroup::test(const NELIterator &i)
+{
+ // An event is a candidate for being within the bounds of the
+ // set if it's simply within the same bar as the original event.
+ // (Groups may contain other groups, so our bounds may enclose
+ // events that aren't members of the group: we reject those in
+ // sample rather than test, so as to avoid erroneously ending
+ // the group too soon.)
+
+ return ((*i)->getViewAbsoluteTime() >= m_barRange.first &&
+ (*i)->getViewAbsoluteTime() < m_barRange.second);
+}
+
+bool
+NotationGroup::sample(const NELIterator &i, bool goingForwards)
+{
+ if (m_baseIterator == getContainer().end()) {
+ m_baseIterator = i;
+ if (m_userSamples)
+ m_initial = i;
+ }
+ if (m_userSamples)
+ m_final = i;
+
+ std::string t;
+ if (!(*i)->event()->get<String>(BaseProperties::BEAMED_GROUP_TYPE, t)) {
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for non-beamed element" << endl;
+ return false;
+ }
+
+ long n;
+ if (!(*i)->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID, n)) return false;
+ if (m_groupNo == -1) {
+ m_groupNo = n;
+ } else if (n != m_groupNo) {
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for event with group id " << n << " (mine is " << m_groupNo << ")" << endl;
+ return false;
+ }
+
+ if (t == BaseProperties::GROUP_TYPE_BEAMED) {
+ m_type = Beamed;
+ } else if (t == BaseProperties::GROUP_TYPE_TUPLED) {
+ m_type = Tupled;
+ } else if (t == BaseProperties::GROUP_TYPE_GRACE) {
+ std::cerr << "NotationGroup::NotationGroup: WARNING: Obsolete group type Grace found" << std::endl;
+ return false;
+ } else {
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: Warning: Rejecting sample() for unknown GroupType \"" << t << "\"" << endl;
+ return false;
+ }
+
+ NOTATION_DEBUG << "NotationGroup::sample: group id is " << m_groupNo << endl;
+
+ AbstractSet<NotationElement, NotationElementList>::sample
+ (i, goingForwards);
+
+ // If the sum of the distances from the middle line to the notes
+ // above the middle line exceeds the sum of the distances from the
+ // middle line to the notes below, then the beam goes below. We
+ // can calculate the weightings here, as we construct the group.
+
+ if (!static_cast<NotationElement*>(*i)->isNote())
+ return true;
+ if (m_userSamples) {
+ if (m_initialNote == getContainer().end()) m_initialNote = i;
+ m_finalNote = i;
+ }
+
+ // The code that uses the Group should not rely on the presence of
+ // e.g. BEAM_GRADIENT to indicate that a beam should be drawn;
+ // it's possible the gradient might be left over from a previous
+ // calculation and the group might have changed since. Instead it
+ // should test BEAMED, which may be false even if there is a
+ // gradient present.
+ (*i)->event()->setMaybe<Bool>(NotationProperties::BEAMED, false);
+
+ int h = height(i);
+ if (h > 4)
+ m_weightAbove += h - 4;
+ if (h < 4)
+ m_weightBelow += 4 - h;
+
+ return true;
+}
+
+bool
+NotationGroup::contains(const NELIterator &i) const
+{
+ NELIterator j(getInitialElement()),
+ k( getFinalElement());
+
+ for (;;) {
+ if (j == i)
+ return true;
+ if (j == k)
+ return false;
+ ++j;
+ }
+}
+
+int
+NotationGroup::height(const NELIterator &i) const
+{
+ long h = 0;
+ if ((*i)->event()->get<Int>(NotationProperties::HEIGHT_ON_STAFF, h)) {
+ return h;
+ }
+
+ //!!! int pitch = (*i)->event()->get<Int>(PITCH);
+ // NotationDisplayPitch p(pitch, m_clef, m_key);
+ // h = p.getHeightOnStaff();
+
+ try {
+ Pitch pitch(*getAsEvent(i));
+ h = pitch.getHeightOnStaff(m_clef, m_key);
+ } catch (...) {
+ // no pitch!
+ }
+
+ // not setMaybe, as we know the property is absent:
+ (*i)->event()->set<Int>(NotationProperties::HEIGHT_ON_STAFF, h, false);
+ return h;
+}
+
+void
+NotationGroup::applyStemProperties()
+{
+ NotationRules rules;
+
+ NELIterator
+ initialNote(getInitialNote()),
+ finalNote(getFinalNote());
+
+ if (initialNote == getContainer().end() ||
+ initialNote == finalNote) {
+ //!!! This is not true -- if initialNote == finalNote there is
+ // one note in the group, not none. But we still won't have a
+ // beam.
+ NOTATION_DEBUG << "NotationGroup::applyStemProperties: no notes in group"
+ << endl;
+ return; // no notes, no case to answer
+ }
+
+ if (getHighestNote() == getContainer().end()) {
+ std::cerr << "ERROR: NotationGroup::applyStemProperties: no highest note!" << std::endl;
+ abort();
+ }
+
+ if (getLowestNote() == getContainer().end()) {
+ std::cerr << "ERROR: NotationGroup::applyStemProperties: no lowest note!" << std::endl;
+ abort();
+ }
+
+ int up = 0, down = 0;
+
+ for (NELIterator i = initialNote; i != getContainer().end(); ++i) {
+ NotationElement* el = static_cast<NotationElement*>(*i);
+ if (el->isNote()) {
+ if (el->event()->has(NotationProperties::STEM_UP)) {
+ if (el->event()->get<Bool>(NotationProperties::STEM_UP)) ++up;
+ else ++down;
+ }
+ }
+
+ if (i == finalNote) break;
+ }
+
+ NOTATION_DEBUG << "NotationGroup::applyStemProperties: weightAbove "
+ << m_weightAbove << ", weightBelow " << m_weightBelow
+ << ", up " << up << ", down " << down << endl;
+
+ bool aboveNotes = rules.isBeamAbove(height(getHighestNote()),
+ height(getLowestNote()),
+ m_weightAbove,
+ m_weightBelow);
+ if (up != down) {
+ if (up > down) aboveNotes = true;
+ else aboveNotes = false;
+ }
+
+ NOTATION_DEBUG << "NotationGroup::applyStemProperties: hence aboveNotes "
+ << aboveNotes << endl;
+
+ /*!!!
+ if ((*initialNote)->event()->has(STEM_UP) &&
+ (*initialNote)->event()->isPersistent<Bool>(STEM_UP)) {
+ aboveNotes = (*initialNote)->event()->get<Bool>(STEM_UP);
+ }
+
+ if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE) &&
+ (*initialNote)->event()->isPersistent<Bool>(NotationProperties::BEAM_ABOVE)) {
+ aboveNotes = (*initialNote)->event()->get<Bool>
+ (NotationProperties::BEAM_ABOVE);
+ }
+ */
+ for (NELIterator i = initialNote; i != getContainer().end(); ++i) {
+
+ NotationElement* el = static_cast<NotationElement*>(*i);
+
+ el->event()->setMaybe<Bool>(NotationProperties::BEAM_ABOVE, aboveNotes);
+
+ if (el->isNote() &&
+ el->event()->has(BaseProperties::NOTE_TYPE) &&
+ el->event()->get<Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet &&
+ el->event()->has(BaseProperties::BEAMED_GROUP_ID) &&
+ el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) {
+
+ el->event()->setMaybe<Bool>(NotationProperties::BEAMED, true);
+ // el->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, aboveNotes);
+
+ } else if (el->isNote()) {
+
+ if (i == initialNote || i == finalNote) {
+ (*i)->event()->setMaybe<Bool>
+ (m_properties.VIEW_LOCAL_STEM_UP, aboveNotes);
+ } else {
+ (*i)->event()->setMaybe<Bool>
+ (m_properties.VIEW_LOCAL_STEM_UP, !aboveNotes);
+ }
+ }
+
+ if (i == finalNote) break;
+ }
+}
+
+bool
+NotationGroup::haveInternalRest()
+const
+{
+ bool inside = false;
+ bool found = false;
+
+ for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) {
+ NotationElement* el = static_cast<NotationElement*>(*i);
+
+ if (el->isNote() &&
+ el->event()->has(BaseProperties::NOTE_TYPE) &&
+ el->event()->get<Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet &&
+ el->event()->has(BaseProperties::BEAMED_GROUP_ID) &&
+ el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) {
+ if (found) return true; // a rest is wholly enclosed by beamed notes
+ inside = true;
+ }
+
+ if (el->isRest() && inside) found = true;
+
+ if (i == getFinalNote()) break;
+ }
+
+ return false;
+}
+
+NotationGroup::Beam
+NotationGroup::calculateBeam(NotationStaff &staff)
+{
+ NotationRules rules;
+
+ Beam beam;
+ beam.aboveNotes = true;
+ beam.startY = 0;
+ beam.gradient = 0;
+ beam.necessary = false;
+
+ NELIterator
+ initialNote(getInitialNote()),
+ finalNote(getFinalNote());
+
+ if (initialNote == getContainer().end() ||
+ initialNote == finalNote) {
+ return beam; // no notes, or at most one: no case to answer
+ }
+
+ beam.aboveNotes = rules.isBeamAbove(height(getHighestNote()),
+ height(getLowestNote()),
+ m_weightAbove,
+ m_weightBelow);
+
+ if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE)) {
+ beam.aboveNotes = (*initialNote)->event()->get
+ <Bool>
+ (NotationProperties::BEAM_ABOVE);
+ }
+
+ timeT crotchet = Note(Note::Crotchet).getDuration();
+
+ beam.necessary =
+ (*initialNote)->getViewDuration() < crotchet &&
+ (*finalNote)->getViewDuration() < crotchet;
+
+ beam.necessary = beam.necessary &&
+ (((*finalNote)->getViewAbsoluteTime() >
+ (*initialNote)->getViewAbsoluteTime()) ||
+ (((*finalNote)->getViewAbsoluteTime() ==
+ (*initialNote)->getViewAbsoluteTime()) &&
+ ((*finalNote)->event()->getSubOrdering() >
+ (*initialNote)->event()->getSubOrdering())));
+
+ // We continue even if the beam is not necessary, because the
+ // same data is used to generate the tupling line in tupled
+ // groups that do not have beams
+
+ // if (!beam.necessary) return beam;
+
+ NOTATION_DEBUG << "NotationGroup::calculateBeam: beam necessariness: " << beam.necessary << endl;
+
+ NotationChord initialChord(getContainer(), initialNote, &getQuantizer(),
+ m_properties, m_clef, m_key),
+ finalChord(getContainer(), finalNote, &getQuantizer(),
+ m_properties, m_clef, m_key);
+
+ if (initialChord.getInitialElement() == finalChord.getInitialElement()) {
+ return beam;
+ }
+
+ bool isGrace =
+ (*initialNote)->event()->has(BaseProperties::IS_GRACE_NOTE) &&
+ (*initialNote)->event()->get<Bool>(BaseProperties::IS_GRACE_NOTE);
+
+ int initialHeight, finalHeight, extremeHeight;
+ NELIterator extremeNote;
+
+ if (beam.aboveNotes) {
+
+ initialHeight = height(initialChord.getHighestNote());
+ finalHeight = height( finalChord.getHighestNote());
+ extremeHeight = height( getHighestNote());
+ extremeNote = getHighestNote();
+
+ } else {
+ initialHeight = height(initialChord.getLowestNote());
+ finalHeight = height( finalChord.getLowestNote());
+ extremeHeight = height( getLowestNote());
+ extremeNote = getLowestNote();
+ }
+
+ int diff = initialHeight - finalHeight;
+ if (diff < 0) diff = -diff;
+
+ bool linear =
+ (beam.aboveNotes ?
+ (extremeHeight <= std::max(initialHeight, finalHeight)) :
+ (extremeHeight >= std::min(initialHeight, finalHeight)));
+
+ if (!linear) {
+ if (diff > 2)
+ diff = 1;
+ else
+ diff = 0;
+ }
+
+ // Now, we need to judge the height of the beam such that the
+ // nearest note of the whole group, the nearest note of the first
+ // chord and the nearest note of the final chord are all at least
+ // two note-body-heights away from it, and at least one of the
+ // start and end points is at least the usual note stem-length
+ // away from it. This is a straight-line equation y = mx + c,
+ // where we have m and two x,y pairs and need to find c.
+
+ //!!! If we find that making one extreme a sensible distance from
+ //the note head makes the other extreme way too far away from it
+ //in the direction of the gradient, then we should flatten the
+ //gradient. There may be a better heuristic for this.
+
+ int initialX = (int)(*initialNote)->getLayoutX();
+ int finalDX = (int) (*finalNote)->getLayoutX() - initialX;
+ int extremeDX = (int)(*extremeNote)->getLayoutX() - initialX;
+
+ int spacing = staff.getNotePixmapFactory(isGrace).getLineSpacing();
+
+ beam.gradient = 0;
+ if (finalDX > 0) {
+ do {
+ if (diff == 0)
+ break;
+ else if (diff > 3)
+ diff = 3;
+ else
+ --diff;
+ beam.gradient = (diff * spacing * 100) / (finalDX * 2);
+ } while (beam.gradient > 18);
+ } else {
+ beam.gradient = 0;
+ }
+ if (initialHeight < finalHeight)
+ beam.gradient = -beam.gradient;
+
+ int finalY = staff.getLayoutYForHeight(finalHeight);
+ int extremeY = staff.getLayoutYForHeight(extremeHeight);
+
+ int c0 = staff.getLayoutYForHeight(initialHeight), c1, c2;
+ double dgrad = (double)beam.gradient / 100.0;
+
+ Equation::solve(Equation::C, extremeY, dgrad, extremeDX, c1);
+ Equation::solve(Equation::C, finalY, dgrad, finalDX, c2);
+
+ using std::max;
+ using std::min;
+ long shortestNoteType = Note::Quaver;
+ if (!(*getShortestElement())->event()->get
+ <Int>(BaseProperties::NOTE_TYPE,
+ shortestNoteType)) {
+ NOTATION_DEBUG << "NotationGroup::calculateBeam: WARNING: Shortest element has no note-type; should this be possible?" << endl;
+ NOTATION_DEBUG << "(Event dump follows)" << endl;
+ (*getShortestElement())->event()->dump(std::cerr);
+ }
+
+ // minimal stem lengths at start, middle-extreme and end of beam
+ int sl = staff.getNotePixmapFactory(isGrace).getStemLength();
+ int ml = spacing * 2;
+ int el = sl;
+
+ NOTATION_DEBUG << "c0: " << c0 << ", c1: " << c1 << ", c2: " << c2 << endl;
+ NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl;
+
+ // If the stems are down, we will need to ensure they end at
+ // heights lower than 0 if there's an internal rest -- likewise
+ // with stems up and an internal rest we need to ensure they end
+ // at higher than 8.
+ // [Avoid doing expensive haveInternalRest() test where possible]
+
+ if (beam.aboveNotes) {
+
+ int topY = staff.getLayoutYForHeight(8);
+
+ if ((c0 - sl > topY) || (c1 - ml > topY) || (c2 - el > topY)) {
+ if (haveInternalRest()) {
+ if (c0 - sl > topY) sl = c0 - topY;
+ if (c1 - ml > topY) ml = c1 - topY;
+ if (c2 - el > topY) el = c2 - topY;
+ NOTATION_DEBUG << "made internal rest adjustment for above notes" << endl;
+ NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl;
+ }
+ }
+ } else {
+ int bottomY = staff.getLayoutYForHeight(0);
+
+ if ((c0 + sl < bottomY) || (c1 + ml < bottomY) || (c2 + el < bottomY)) {
+ if (haveInternalRest()) {
+ if (c0 + sl < bottomY) sl = bottomY - c0;
+ if (c1 + ml < bottomY) ml = bottomY - c1;
+ if (c2 + el < bottomY) el = bottomY - c2;
+ NOTATION_DEBUG << "made internal rest adjustment for below notes" << endl;
+ NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl;
+ }
+ }
+ }
+
+
+ if (shortestNoteType < Note::Semiquaver) {
+ int off = spacing * (Note::Semiquaver - shortestNoteType);
+ sl += off;
+ el += off;
+ }
+
+ if (shortestNoteType < Note::Quaver) {
+ int off = spacing * (Note::Quaver - shortestNoteType);
+ ml += off;
+ }
+
+
+ int midY = staff.getLayoutYForHeight(4);
+
+ // ensure extended to middle line if necessary, and assign suitable stem length
+ if (beam.aboveNotes) {
+ if (c0 - sl > midY) sl = c0 - midY;
+ if (c1 - ml > midY) ml = c1 - midY;
+ if (c2 - el > midY) el = c2 - midY;
+ if (extremeDX > 1.0 || extremeDX < -1.0) {
+ // beam.gradient = int(100 * double(c2 - c0) / double(extremeDX));
+ }
+ beam.startY = min(min(c0 - sl, c1 - ml), c2 - el);
+ } else {
+ if (c0 + sl < midY) sl = midY - c0;
+ if (c1 + ml < midY) ml = midY - c1;
+ if (c2 + el < midY) el = midY - c2;
+ if (extremeDX > 1.0 || extremeDX < -1.0) {
+ // beam.gradient = int(100 * double(c2 - c0) / double(extremeDX));
+ }
+ beam.startY = max(max(c0 + sl, c1 + ml), c2 + el);
+ }
+ /*
+ NOTATION_DEBUG << "NotationGroup::calculateBeam: beam data:" << endl
+ << "gradient: " << beam.gradient << endl
+ << "(c0 " << c0 << ", c2 " << c2 << ", extremeDX " << extremeDX << ")" << endl
+ << "startY: " << beam.startY << endl
+ << "aboveNotes: " << beam.aboveNotes << endl
+ << "shortestNoteType: " << shortestNoteType << endl
+ << "necessary: " << beam.necessary << endl;
+ */
+ return beam;
+}
+
+void
+NotationGroup::applyBeam(NotationStaff &staff)
+{
+ // NOTATION_DEBUG << "NotationGroup::applyBeam, group no is " << m_groupNo << endl;
+ /*
+ NOTATION_DEBUG << "\nNotationGroup::applyBeam" << endl;
+ NOTATION_DEBUG << "Group id: " << m_groupNo << ", type " << m_type << endl;
+ NOTATION_DEBUG << "Coverage:" << endl;
+ int i = 0;
+ for (NELIterator i = getInitialElement(); i != getContainer().end(); ++i) {
+ (*i)->event()->dump(cerr);
+ if (i == getFinalElement()) break;
+ }
+ {
+ NELIterator i(getInitialNote());
+ NOTATION_DEBUG << "Initial note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ {
+ NELIterator i(getFinalNote());
+ NOTATION_DEBUG << "Final note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ {
+ NELIterator i(getHighestNote());
+ NOTATION_DEBUG << "Highest note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ {
+ NELIterator i(getLowestNote());
+ NOTATION_DEBUG << "Lowest note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ */
+ Beam beam(calculateBeam(staff));
+ if (!beam.necessary) {
+ for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) {
+ (*i)->event()->unset(NotationProperties::BEAMED);
+ (*i)->event()->unset(m_properties.TUPLING_LINE_MY_Y);
+ if (i == getFinalNote())
+ break;
+ }
+ return ;
+ }
+
+ // NOTATION_DEBUG << "NotationGroup::applyBeam: Beam is necessary" << endl;
+
+ NELIterator initialNote(getInitialNote()),
+ finalNote( getFinalNote());
+ int initialX = (int)(*initialNote)->getLayoutX();
+ timeT finalTime = (*finalNote)->getViewAbsoluteTime();
+
+ // For each chord in the group, we nominate the note head furthest
+ // from the beam as the primary note, the one that "owns" the stem
+ // and the section of beam up to the following chord. For this
+ // note, we need to:
+ //
+ // * Set the start height, start x-coord and gradient of the beam
+ // (we can't set the stem length for this note directly, because
+ // we don't know its y-coordinate yet)
+ //
+ // * Set width of this section of beam
+ //
+ // * Set the number of beams required for the following note (one
+ // slight complication here: a beamed group in which the very
+ // first chord is shorter than the following one. Here the first
+ // chord needs to know it's the first, or else it can't draw the
+ // part-beams immediately to its right correctly.)
+ //
+ // For the rest of the notes in the chord, we just need to
+ // indicate that they aren't part of the beam-drawing process and
+ // don't need to draw a stem.
+
+ NELIterator prev = getContainer().end(), prevprev = getContainer().end();
+ double gradient = (double)beam.gradient / 100.0;
+
+ // NOTATION_DEBUG << "NotationGroup::applyBeam starting for group "<< this << endl;
+
+ for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) {
+ NotationElement* el = static_cast<NotationElement*>(*i);
+
+ // Clear tuplingness for all events in the group, to be
+ // reinstated by any subsequent call to applyTuplingLine. We
+ // do this because applyTuplingLine doesn't clear these
+ // properties from notes that don't need them; it only applies
+ // them to notes that do.
+ el->event()->unset(m_properties.TUPLING_LINE_MY_Y);
+
+ if (el->isNote() &&
+ el->event()->has(BaseProperties::NOTE_TYPE) &&
+ el->event()->get
+ <Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet &&
+ el->event()->has(BaseProperties::BEAMED_GROUP_ID) &&
+ el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) {
+
+ NotationChord chord(getContainer(), i, &getQuantizer(),
+ m_properties, m_clef, m_key);
+ unsigned int j;
+
+ // NOTATION_DEBUG << "NotationGroup::applyBeam: Found chord" << endl;
+
+ bool hasShifted = chord.hasNoteHeadShifted();
+
+ for (j = 0; j < chord.size(); ++j) {
+ NotationElement *el = static_cast<NotationElement*>(*chord[j]);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.CHORD_PRIMARY_NOTE, false);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.DRAW_FLAG, false);
+
+ el->event()->setMaybe<Bool>
+ (NotationProperties::BEAMED, true);
+
+ el->event()->setMaybe<Bool>
+ (NotationProperties::BEAM_ABOVE, beam.aboveNotes);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes);
+
+ bool shifted = chord.isNoteHeadShifted(chord[j]);
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_HEAD_SHIFTED, shifted);
+
+ long dots = 0;
+ (void)el->event()->get
+ <Int>(BaseProperties::NOTE_DOTS, dots);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_DOT_SHIFTED, false);
+ if (hasShifted && beam.aboveNotes) {
+ long dots = 0;
+ (void)el->event()->get
+ <Int>(BaseProperties::NOTE_DOTS, dots);
+ if (dots > 0) {
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_DOT_SHIFTED, true);
+ }
+ }
+
+ el->event()->setMaybe<Bool>
+ (m_properties.NEEDS_EXTRA_SHIFT_SPACE,
+ chord.hasNoteHeadShifted() && !beam.aboveNotes);
+ }
+
+ if (beam.aboveNotes)
+ j = 0;
+ else
+ j = chord.size() - 1;
+
+ NotationElement *el = static_cast<NotationElement*>(*chord[j]);
+ el->event()->setMaybe<Bool>(NotationProperties::BEAMED, false); // set later
+ el->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, true); // set later
+
+ int x = (int)el->getLayoutX();
+ int myY = (int)(gradient * (x - initialX)) + beam.startY;
+
+ int beamCount =
+ NoteStyleFactory::getStyleForEvent(el->event())->
+ getFlagCount(el->event()->get
+ <Int>(BaseProperties::NOTE_TYPE));
+
+ // If THIS_PART_BEAMS is true, then when drawing the
+ // chord, if it requires more beams than the following
+ // chord then they should be added as partial beams to the
+ // right of the stem.
+
+ // If NEXT_PART_BEAMS is true, then when drawing the
+ // chord, if it requires fewer beams than the following
+ // chord then the difference should be added as partial
+ // beams to the left of the following chord's stem.
+
+ // Procedure for setting these: If we have more beams than
+ // the preceding chord, then the preceding chord should
+ // have NEXT_PART_BEAMS set, until possibly unset again on
+ // the next iteration. If we have at least as many beams
+ // as the preceding chord, then the preceding chord should
+ // have THIS_PART_BEAMS unset and the one before it should
+ // have NEXT_PART_BEAMS unset. The first chord should
+ // have THIS_PART_BEAMS set, until possibly unset again on
+ // the next iteration.
+
+ if (prev != getContainer().end()) {
+
+ NotationElement *prevEl = static_cast<NotationElement*>(*prev);
+ int secWidth = x - (int)prevEl->getLayoutX();
+
+ // prevEl->event()->setMaybe<Int>(BEAM_NEXT_Y, myY);
+
+ prevEl->event()->setMaybe<Int>
+ (m_properties.BEAM_SECTION_WIDTH, secWidth);
+ prevEl->event()->setMaybe<Int>
+ (m_properties.BEAM_NEXT_BEAM_COUNT, beamCount);
+
+ int prevBeamCount =
+ NoteStyleFactory::getStyleForEvent(prevEl->event())->
+ getFlagCount(prevEl->event()->get
+ <Int>(BaseProperties::NOTE_TYPE));
+
+ if ((beamCount > 0) && (prevBeamCount > 0)) {
+ el->event()->setMaybe<Bool>(m_properties.BEAMED, true);
+ el->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, false);
+ prevEl->event()->setMaybe<Bool>(m_properties.BEAMED, true);
+ prevEl->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, false);
+ }
+
+ if (beamCount >= prevBeamCount) {
+ prevEl->event()->setMaybe<Bool>
+ (m_properties.BEAM_THIS_PART_BEAMS, false);
+ if (prevprev != getContainer().end()) {
+ (*prevprev)->event()->setMaybe<Bool>
+ (m_properties.BEAM_NEXT_PART_BEAMS, false);
+ }
+ }
+
+ if (beamCount > prevBeamCount) {
+ prevEl->event()->setMaybe<Bool>
+ (m_properties.BEAM_NEXT_PART_BEAMS, true);
+ }
+
+ } else {
+ el->event()->setMaybe<Bool>(m_properties.BEAM_THIS_PART_BEAMS, true);
+ }
+
+ el->event()->setMaybe<Bool>(m_properties.CHORD_PRIMARY_NOTE, true);
+
+ el->event()->setMaybe<Int>(m_properties.BEAM_MY_Y, myY);
+ el->event()->setMaybe<Int>(m_properties.BEAM_GRADIENT, beam.gradient);
+
+ // until they're set next time around the loop, as (*prev)->...
+ // el->event()->setMaybe<Int>(m_properties.BEAM_NEXT_Y, myY);
+ el->event()->setMaybe<Int>(m_properties.BEAM_SECTION_WIDTH, 0);
+ el->event()->setMaybe<Int>(m_properties.BEAM_NEXT_BEAM_COUNT, 1);
+
+ prevprev = prev;
+ prev = chord[j];
+ i = chord.getFinalElement();
+
+ }
+ else if (el->isNote()) {
+
+ //!!! should we really be setting these here as well as in
+ // applyStemProperties?
+ /*
+ if (i == initialNote || i == finalNote) {
+ (*i)->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes);
+ } else {
+ (*i)->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, !beam.aboveNotes);
+ }
+ */
+ }
+
+ if (i == finalNote || el->getViewAbsoluteTime() > finalTime) break;
+ }
+}
+
+void
+NotationGroup::applyTuplingLine(NotationStaff &staff)
+{
+ // NOTATION_DEBUG << "NotationGroup::applyTuplingLine, group no is " << m_groupNo << ", group type is " << m_type << endl;
+
+ if (m_type != Tupled)
+ return ;
+
+ // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: line is necessary" << endl;
+
+ Beam beam(calculateBeam(staff));
+
+ NELIterator
+ initialNote(getInitialNote()),
+ finalNote(getFinalNote()),
+ initialElement(getInitialElement()),
+ finalElement(getFinalElement());
+
+ NELIterator initialNoteOrRest(initialElement);
+ NotationElement* initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest);
+
+ while (initialNoteOrRest != finalElement &&
+ !(initialNoteOrRestEl->isNote() ||
+ initialNoteOrRestEl->isRest())) {
+ ++initialNoteOrRest;
+ initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest);
+ }
+
+ if (!initialNoteOrRestEl->isRest()) {
+ initialNoteOrRest = initialNote;
+ initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest);
+ }
+
+ if (initialNoteOrRest == staff.getViewElementList()->end()) return;
+
+ bool isGrace = false;
+ if (initialNote != staff.getViewElementList()->end()) {
+ isGrace =
+ (*initialNote)->event()->has(BaseProperties::IS_GRACE_NOTE) &&
+ (*initialNote)->event()->get<Bool>(BaseProperties::IS_GRACE_NOTE);
+ }
+
+ // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: first element is " << (initialNoteOrRestEl->isNote() ? "Note" : "Non-Note") << ", last is " << (static_cast<NotationElement*>(*finalElement)->isNote() ? "Note" : "Non-Note") << endl;
+
+ int initialX = (int)(*initialNoteOrRest)->getLayoutX();
+ int finalX = (int)(*finalElement)->getLayoutX();
+
+ if (initialNote == staff.getViewElementList()->end() &&
+ finalNote == staff.getViewElementList()->end()) {
+
+ Event *e = (*initialNoteOrRest)->event();
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_MY_Y,
+ staff.getLayoutYForHeight(12));
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_WIDTH, finalX - initialX);
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_GRADIENT, 0);
+
+ } else {
+
+ // only notes have height
+ int initialY = staff.getLayoutYForHeight(height(initialNote));
+ int finalY = staff.getLayoutYForHeight(height( finalNote));
+
+ // if we have a beam and both end-points of it are notes,
+ // place the tupling number over it (that is, make the tupling
+ // line follow the beam and say so); otherwise make the line
+ // follow the gradient a beam would have, but on the other
+ // side of the notes
+ bool followBeam =
+ (beam.necessary &&
+ (*initialNoteOrRest)->event()->isa(Note::EventType) &&
+ (finalNote == finalElement));
+
+ int startY = (followBeam ? beam.startY :
+ initialY - (beam.startY - initialY));
+
+ int endY = startY + (int)((finalX - initialX) *
+ ((double)beam.gradient / 100.0));
+
+ // NOTATION_DEBUG << "applyTuplingLine: beam.startY is " << beam.startY << ", initialY is " << initialY << " so my startY is " << startY << ", endY " << endY << ", beam.gradient " << beam.gradient << endl;
+
+ int nh = staff.getNotePixmapFactory(isGrace).getNoteBodyHeight();
+
+ if (followBeam) { // adjust to move text slightly away from beam
+
+ int maxEndBeamCount = 1;
+ long bc;
+ if ((*initialNoteOrRest)->event()->get<Int>
+ (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) {
+ if (bc > maxEndBeamCount)
+ maxEndBeamCount = bc;
+ }
+ if ((*finalNote)->event()->get<Int>
+ (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) {
+ if (bc > maxEndBeamCount)
+ maxEndBeamCount = bc;
+ }
+
+ int extraBeamSpace = maxEndBeamCount * nh + nh / 2;
+
+ if (beam.aboveNotes) {
+ startY -= extraBeamSpace;
+ endY -= extraBeamSpace;
+ finalX += nh;
+ } else {
+ startY += extraBeamSpace;
+ endY += extraBeamSpace;
+ finalX -= nh;
+ }
+
+ } else { // adjust to place close to note heads
+
+ if (startY < initialY) {
+ if (initialY - startY > nh * 3)
+ startY = initialY - nh * 3;
+ if ( finalY - endY < nh * 2)
+ startY -= nh * 2 - (finalY - endY);
+ } else {
+ if (startY - initialY > nh * 3)
+ startY = initialY + nh * 3;
+ if ( endY - finalY < nh * 2)
+ startY += nh * 2 - (endY - finalY);
+ }
+ }
+
+ Event *e = (*initialNoteOrRest)->event();
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_MY_Y, startY);
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_WIDTH, finalX - initialX);
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_GRADIENT, beam.gradient);
+ e->setMaybe<Bool>(m_properties.TUPLING_LINE_FOLLOWS_BEAM, followBeam);
+ }
+}
+
+}