diff options
Diffstat (limited to 'src/gui/editors/notation/NotationGroup.cpp')
-rw-r--r-- | src/gui/editors/notation/NotationGroup.cpp | 979 |
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); + } +} + +} |