Skip to content

Commit

Permalink
Refactor beam groups to store a list of stem indices instead of a con…
Browse files Browse the repository at this point in the history
…secutive range.

This will be necessary for grace notes, which shouldn't become part of a
beam group but should also not break up a beam group.

Bug: #19
  • Loading branch information
cameronwhite committed Jun 15, 2015
1 parent 3a7b26d commit a64ba24
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 104 deletions.
22 changes: 12 additions & 10 deletions source/painters/beamgroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ static int getNumExtraBeams(const NoteStem &stem)
std::log(2.0) - 3;
}

BeamGroup::BeamGroup(NoteStem::StemType direction, size_t start, size_t end)
: myStemDirection(direction), myStartIndex(start), myEndIndex(end)
BeamGroup::BeamGroup(NoteStem::StemType direction,
const std::vector<size_t> &stems)
: myStemDirection(direction), myStems(stems)
{
}

Expand All @@ -49,17 +50,18 @@ void BeamGroup::drawStems(QGraphicsItem *parent,
QList<QGraphicsItem *> symbols;
QPainterPath stemPath;

auto begin = stems.begin() + myStartIndex;
auto end = stems.begin() + myEndIndex;
std::vector<NoteStem> group_stems;
for (size_t i : myStems)
group_stems.push_back(stems[i]);

auto begin = group_stems.begin();
auto end = group_stems.end();
const NoteStem &firstStem = *begin;
const NoteStem &lastStem = *(end - 1);
auto numStems = std::distance(begin, end);

// Draw each stem.
for (auto it = begin; it != end; ++it)
for (const NoteStem &stem : group_stems)
{
const NoteStem &stem = *it;

stemPath.moveTo(stem.getX(), stem.getTop());
stemPath.lineTo(stem.getX(), stem.getBottom());

Expand All @@ -86,7 +88,7 @@ void BeamGroup::drawStems(QGraphicsItem *parent,
QPainterPath beamPath;

// Draw connecting line.
if (numStems > 1)
if (group_stems.size() > 1)
{
const double connectorHeight = firstStem.getStemEdge();

Expand All @@ -101,7 +103,7 @@ void BeamGroup::drawStems(QGraphicsItem *parent,
beams->setParentItem(parent);

// Draw a note flag for single notes (eighth notes or less) or grace notes.
if (numStems == 1 && NoteStem::canHaveFlag(firstStem))
if (group_stems.size() == 1 && NoteStem::canHaveFlag(firstStem))
{
QGraphicsItem *flag = createNoteFlag(firstStem, musicFont, fm);
flag->setParentItem(parent);
Expand Down
5 changes: 2 additions & 3 deletions source/painters/beamgroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class QPainterPath;
class BeamGroup
{
public:
BeamGroup(NoteStem::StemType direction, size_t start, size_t end);
BeamGroup(NoteStem::StemType direction, const std::vector<size_t> &stems);

/// Draws the stems for each note in the group.
void drawStems(QGraphicsItem *parent, const std::vector<NoteStem> &stems,
Expand Down Expand Up @@ -63,8 +63,7 @@ class BeamGroup
const QFontMetricsF &fm);

NoteStem::StemType myStemDirection;
size_t myStartIndex;
size_t myEndIndex;
std::vector<size_t> myStems;
};

#endif
70 changes: 70 additions & 0 deletions source/painters/notestem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,73 @@ bool NoteStem::hasMarcato() const
{
return myPosition->hasProperty(Position::Marcato);
}

NoteStem::StemType NoteStem::formatGroup(std::vector<NoteStem> &stems,
const std::vector<size_t> &group)
{
const StemType direction = computeStemDirection(stems, group);

if (direction == StemUp)
{
NoteStem highestStem = NoteStem::findHighestStem(stems, group);

for (size_t i : group)
{
NoteStem &stem = stems[i];
stem.setX(stem.getNoteHeadRightEdge() - 1);
stem.setTop(highestStem.getTop() - highestStem.getStemHeight());
}
}
else // Stem down.
{
NoteStem lowestStem = NoteStem::findLowestStem(stems, group);

for (size_t i : group)
{
NoteStem &stem = stems[i];
stem.setBottom(lowestStem.getBottom() + lowestStem.getStemHeight());
}
}

return direction;
}

NoteStem::StemType NoteStem::computeStemDirection(
std::vector<NoteStem> &stems, const std::vector<size_t> &group)
{
// Find how many stem directions of each type we have.
const size_t stemsUp = std::count_if(group.begin(), group.end(), [&](size_t i) {
return stems[i].getStemType() == NoteStem::StemUp;
});

const size_t stemsDown = std::count_if(group.begin(), group.end(), [&](size_t i) {
return stems[i].getStemType() == NoteStem::StemDown;
});

NoteStem::StemType stemType =
(stemsDown >= stemsUp) ? NoteStem::StemDown : NoteStem::StemUp;

// Assign the new stem direction to each stem.
for (size_t i : group)
stems[i].setStemType(stemType);

return stemType;
}

const NoteStem &NoteStem::findHighestStem(const std::vector<NoteStem> &stems,
const std::vector<size_t> &group)
{
return stems[*std::min_element(
group.begin(), group.end(), [&](size_t i, size_t j) {
return stems[i].getTop() < stems[j].getTop();
})];
}

const NoteStem &NoteStem::findLowestStem(const std::vector<NoteStem> &stems,
const std::vector<size_t> &group)
{
return stems[*std::max_element(
group.begin(), group.end(), [&](size_t i, size_t j) {
return stems[i].getBottom() < stems[j].getBottom();
})];
}
64 changes: 9 additions & 55 deletions source/painters/notestem.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@ class NoteStem

/// Sets a common direction for the stems, and stretches the beams to
/// a common high/low height.
template <typename Iterator>
static StemType formatGroup(Iterator begin, Iterator end);
static StemType formatGroup(std::vector<NoteStem> &stems,
const std::vector<size_t> &group);

static const NoteStem &findHighestStem(const std::vector<NoteStem> &stems,
const std::vector<size_t> &group);
static const NoteStem &findLowestStem(const std::vector<NoteStem> &stems,
const std::vector<size_t> &group);

template <typename Iterator>
static const NoteStem &findHighestStem(Iterator begin, Iterator end);
Expand All @@ -82,8 +87,8 @@ class NoteStem

private:
/// Sets a common direction for the stems, and returns that direction.
template <typename Iterator>
static StemType computeStemDirection(Iterator begin, Iterator end);
static StemType computeStemDirection(std::vector<NoteStem> &stems,
const std::vector<size_t> &group);

const Voice *myVoice;
const Position *myPosition;
Expand All @@ -95,57 +100,6 @@ class NoteStem
bool myFullBeaming;
};

template <typename Iterator>
NoteStem::StemType NoteStem::formatGroup(Iterator begin, Iterator end)
{
const StemType direction = computeStemDirection(begin, end);

if (direction == StemUp)
{
NoteStem highestStem = NoteStem::findHighestStem(begin, end);

for (auto stem = begin; stem != end; ++stem)
{
stem->setX(stem->getNoteHeadRightEdge() - 1);
stem->setTop(highestStem.getTop() - highestStem.getStemHeight());
}
}
else // Stem down.
{
NoteStem lowestStem = NoteStem::findLowestStem(begin, end);

for (auto stem = begin; stem != end; ++stem)
{
stem->setBottom(lowestStem.getBottom() +
lowestStem.getStemHeight());
}
}

return direction;
}

template <typename Iterator>
NoteStem::StemType NoteStem::computeStemDirection(Iterator begin, Iterator end)
{
// Find how many stem directions of each type we have.
const size_t stemsUp = std::count_if(begin, end, [](const NoteStem &stem) {
return stem.getStemType() == NoteStem::StemUp;
});

const size_t stemsDown = std::count_if(begin, end, [](const NoteStem &stem) {
return stem.getStemType() == NoteStem::StemDown;
});

NoteStem::StemType stemType =
(stemsDown >= stemsUp) ? NoteStem::StemDown : NoteStem::StemUp;

// Assign the new stem direction to each stem.
for (auto stem = begin; stem != end; ++stem)
stem->setStemType(stemType);

return stemType;
}

template <typename Iterator>
const NoteStem &NoteStem::findHighestStem(Iterator begin, Iterator end)
{
Expand Down
64 changes: 28 additions & 36 deletions source/painters/stdnotationnote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,63 +355,55 @@ void StdNotationNote::computeBeamingGroups(
// Rests and notes greater than eighth notes will break apart a beam group,
// so we need to find all of the subgroups of consecutive positions that
// can be beamed, and then create beaming groups with those notes.
auto beamableGroupStart = stems.begin() + firstStemIndex;
auto beamableGroupEnd = beamableGroupStart;
auto lastStem = stems.begin() + lastStemIndex;

// Find all subgroups of beamable notes (i.e. consecutive notes that aren't
// quarter notes, rests, etc).
while (beamableGroupEnd != lastStem)
size_t i = firstStemIndex;
while (i != lastStemIndex)
{
// Find the next range of consecutive stems that are beamable.
beamableGroupStart = std::find_if(beamableGroupEnd, lastStem,
&NoteStem::isBeamable);

// If there were any notes that aren't beamed but need stems (like a
// half note or a quarter note), create a single-item beam group for
// each.
for (auto it = beamableGroupEnd; it != beamableGroupStart; ++it)
while (i < lastStemIndex && !NoteStem::isBeamable(stems[i]))
{
if (NoteStem::needsStem(*it))
// If there were any notes that aren't beamed but need stems (like a
// half note or a quarter note), create a single-item beam group for
// each.
if (NoteStem::needsStem(stems[i]))
{
auto direction = NoteStem::formatGroup(it, it + 1);
groups.push_back(BeamGroup(direction, it - stems.begin(),
it + 1 - stems.begin()));
groups.push_back(
BeamGroup(NoteStem::formatGroup(stems, { i }), { i }));
}

++i;
}

// Find the end of the beam group.
beamableGroupEnd = std::find_if(beamableGroupStart, lastStem,
std::not1(std::ptr_fun(&NoteStem::isBeamable)));

if (beamableGroupStart != beamableGroupEnd)
std::vector<size_t> group_stems;
while (i < lastStemIndex && NoteStem::isBeamable(stems[i]))
{
// Set up divisions within the beam group at each beat. For example,
// with a group of 8 16th notes in 4/4 time, the 5th note should not
// be fully beamed to the previous note.
for (auto it = boost::next(beamableGroupStart);
it != beamableGroupEnd; ++it)
if (!group_stems.empty())
{
if (subgroupLength)
{
const int i = std::distance(
stems.begin() + firstStemIndexInBar, boost::prior(it));

if (std::abs(std::fmod(durations[i], *subgroupLength)) >=
0.001)
if (std::abs(
std::fmod(durations[i - firstStemIndexInBar - 1],
*subgroupLength)) >= 0.001)
{
it->setFullBeaming(true);
stems[i].setFullBeaming(true);
}
}
else
it->setFullBeaming(true);
stems[i].setFullBeaming(true);
}

auto direction = NoteStem::formatGroup(beamableGroupStart,
beamableGroupEnd);
groups.push_back(BeamGroup(direction,
beamableGroupStart - stems.begin(),
beamableGroupEnd - stems.begin()));
group_stems.push_back(i);
++i;
}

// Record the beam group.
if (!group_stems.empty())
{
auto direction = NoteStem::formatGroup(stems, group_stems);
groups.push_back(BeamGroup(direction, group_stems));
}
}
}
Expand Down

0 comments on commit a64ba24

Please sign in to comment.