Skip to content

Commit

Permalink
Separate voices during Real-time entry
Browse files Browse the repository at this point in the history
  • Loading branch information
shoogle committed Aug 22, 2016
1 parent 9843867 commit 1fbbfb3
Show file tree
Hide file tree
Showing 9 changed files with 513 additions and 5 deletions.
2 changes: 1 addition & 1 deletion libmscore/CMakeLists.txt
Expand Up @@ -66,7 +66,7 @@ add_library (
synthesizerstate.cpp mcursor.cpp groups.cpp mscoreview.cpp
noteline.cpp spannermap.cpp
bagpembell.cpp ambitus.cpp keylist.cpp scoreElement.cpp
shape.cpp systemdivider.cpp midimapping.cpp
shape.cpp systemdivider.cpp midimapping.cpp virtualmeasure.cpp
)

set_target_properties (
Expand Down
116 changes: 115 additions & 1 deletion libmscore/edit.cpp
Expand Up @@ -56,6 +56,7 @@
#include "ottava.h"
#include "textframe.h"
#include "accidental.h"
#include "virtualmeasure.h"

namespace Ms {

Expand Down Expand Up @@ -1471,7 +1472,7 @@ void Score::regroupNotesAndRests(int startTick, int endTick, int track)
int maxTick = endTick > msr->endTick() ? msr->endTick() : endTick;
if (!seg || seg->measure() != msr)
seg = msr->first(Segment::Type::ChordRest);
for (; seg && seg->tick() + seg->ticks() < maxTick; seg = seg->next(Segment::Type::ChordRest)) {
for (; seg && seg->tick() + seg->ticks() <= maxTick; seg = seg->next(Segment::Type::ChordRest)) {
ChordRest* curr = seg->cr(track);
if (!curr)
continue; // voice is empty here
Expand Down Expand Up @@ -1533,6 +1534,119 @@ void Score::regroupNotesAndRests(int startTick, int endTick, int track)
_is.setSegment(inputSegment);
}

//---------------------------------------------------------
// recalculateVoices
//---------------------------------------------------------

void Score::recalculateVoices(int startTick, int endTick, int staffIdx)
{
// store InputState location so we can get back to it later.
Segment* inputSegment = _is.segment();
int inputTrack = _is.track();
Segment* startSeg = tick2segment(startTick, true, Segment::Type::ChordRest);
for (Measure* msr = startSeg->measure(); msr && msr->tick() < endTick; msr = msr->nextMeasure()) {
Segment* firstSeg = startSeg->measure() == msr ? startSeg : msr->first(Segment::Type::ChordRest);
int maxTick = endTick < msr->endTick() ? endTick : msr->endTick();
VirtualMeasure vm; // temporary place to put notes while the new voicing is calculated
for (Segment* seg = firstSeg; seg && seg->tick() + seg->ticks() <= maxTick; seg = seg->next(Segment::Type::ChordRest)) {
int sTrack = staffIdx * VOICES;
int eTrack = sTrack + VOICES;
for (int track = sTrack; track < eTrack; track++) {
ChordRest* cr = seg->cr(track);
if (!cr || !cr->isChord())
continue; // voice is empty here
// get length of tied note
Chord* chord = toChord(cr);
for (Note* n : chord->notes()) {
Tie* t = n->tieBack();
Note* tnb = t ? t->startNote() : 0;
if (tnb && tnb->chord()->measure() == chord->measure())
continue; // already accounted for this note
int ticks = chord->duration().ticks();
Note* tnf = 0;
Note* nn = n;
for (t = n->tieFor(); t; t = nn->tieFor()) {
nn = t->endNote();
if (nn->tick() + nn->chord()->duration().ticks() <= maxTick) {
ticks += nn->chord()->duration().ticks();
}
else {
tnf = nn;
break;
}
}
TimeSigFrac nominal = sigmap()->timesig(seg->tick()).nominal();
std::vector<TDuration> dList = toRhythmicDurationList(
Fraction::fromTicks(ticks), false, n->rtick(), nominal, seg->measure(), 1);
vm.addTiedNotes(n->pitch(), n->tick(), dList, tnf, tnb); // add notes to temporary container
}
}
}
// calculate the new voicing
vm.arrange();
// overwrite the real measure with the newly calculated one
for (int voice = 0, track = staffIdx * VOICES; voice < VOICES; voice++, track++) {
doLayoutRange(startSeg->tick(), maxTick);
Segment* seg = firstSeg;
if (voice >= vm.numVoices()) {
// no more vvoices so overwrite remaining voices with rests
seg = setNoteRest(seg, track, NoteVal(), Fraction::fromTicks(maxTick - seg->tick()), Direction::AUTO);
continue;
}
VirtualVoice* vvoice = vm.voices()[voice];
for (RawChord* rchord : vvoice->chords()) {
if (seg->tick() < rchord->tick()) {
seg = setNoteRest(seg, track, NoteVal(), Fraction::fromTicks(rchord->tick() - seg->tick()), Direction::AUTO); // This *appears* to work in all voices, but see FIXME below.
while (seg->tick() < rchord->tick()) // might encounter segments from other voices before the one we want
seg = seg->next(Segment::Type::ChordRest); // FIXME: in higher voices this sometimes does not find the ChordRest we just added 2 lines above with setNoteRest()
}
seg = setNoteRest(seg, track, NoteVal(0), Fraction::fromTicks(rchord->ticks()), Direction::AUTO);
Chord* newChord = toChord(seg->cr(track));
Note* n = newChord->notes().front();
undoRemoveElement(n);
for (RawNote* rnote : rchord->notes()) {
NoteVal nval = NoteVal(rnote->pitch());
n = addNote(newChord, nval);
Note* nn = rnote->tiedNoteFor();
if (nn) {
Tie* t = new Tie(this);
n->setTieFor(t);
t->setStartNote(n);
t->setEndNote(nn);
nn->setTieBack(t);
t->setTrack(track);
undoAddElement(t);
}
else if (rnote->next()) {
rnote->next()->setTiedNoteBack(n);
}
nn = rnote->tiedNoteBack();
if (nn) {
Tie* t = new Tie(this);
n->setTieBack(t);
t->setEndNote(n);
t->setStartNote(nn);
nn->setTieFor(t);
t->setTrack(nn->track());
undoAddElement(t);
}
else if (rnote->prev()) {
rnote->prev()->setTiedNoteFor(n);
}
}
while (seg && seg->tick() < rchord->endTick()) // might encounter segments from other voices before the one we want
seg = seg->next(Segment::Type::ChordRest);
}
if (seg && seg->tick() < maxTick) // measure is incomplete so fill with rests
seg = setNoteRest(seg, track, NoteVal(), Fraction::fromTicks(maxTick - seg->tick()), Direction::AUTO);
}
}
// now put the input state back where it was before
_is.setTrack(inputTrack);
_is.setSegment(inputSegment);
}


//---------------------------------------------------------
// cmdAddTie
//---------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions libmscore/score.h
Expand Up @@ -663,6 +663,7 @@ class Score : public QObject, public ScoreElement {

void repitchNote(const Position& pos, bool replace);
void regroupNotesAndRests(int startTick, int endTick, int track);
void recalculateVoices(int startTick, int endTick, int staffIdx);
void cmdAddPitch(int pitch, bool addFlag, bool insert);
void cmdTimeDelete();
void timeDelete(Measure*, Segment*, const Fraction&);
Expand Down
217 changes: 217 additions & 0 deletions libmscore/virtualmeasure.cpp
@@ -0,0 +1,217 @@
//=============================================================================
// virtualmeasure.cpp
//
// Copyright (C) 2016 Peter Jonas <shoogle@users.noreply.github.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================

#include <algorithm> // std::sort

#include "virtualmeasure.h"
#include "durationtype.h"

namespace Ms {

//---------------------------------------------------------
// arrange
// Re-arrange the passage of music in the Virtual Measure.
//---------------------------------------------------------

void VirtualMeasure::arrange()
{
// Notes were already added to lowest available voice, so the arangement is already optimum.
// Just need to sort the voices to ensure that voices 1 and 3 are higher than voices 2 and 4.
for (VirtualVoice* vv: _voices) {
vv->sortChordsByTick();
for (RawChord* rc : vv->chords()) {
rc->sortNotesByPitch();
}
}

sortVoicesByAveragePitch();
}

//---------------------------------------------------------
// addTiedNotes
// Adds a note for each duration in the list and ties it to the others in the list.
//---------------------------------------------------------

void VirtualMeasure::addTiedNotes(int pitch, int tick, const std::vector<TDuration>& dList, Note* tiedNoteFor, Note* tiedNoteBack)
{
RawNote* prev = 0;
int cumulativeTick = tick;
for (TDuration d : dList) {
int ticks = d.ticks();
RawNote* rn = new RawNote(pitch, 0, tiedNoteBack, 0, prev);
RawChord* rc = new RawChord(cumulativeTick, ticks, rn);
addChord(rc);
if (prev)
prev->setNext(rn); // update the previous note to point to this one
prev = rn;
tiedNoteBack = 0;
cumulativeTick += ticks;
}
prev->setTiedNoteFor(tiedNoteFor); // tie the final note to a note outside the VirtualMeasure
}

//---------------------------------------------------------
// addChord
// Adds a chord to the lowest-numbered voice it can go in.
//---------------------------------------------------------

void VirtualMeasure::addChord(RawChord* chord)
{
for (VirtualVoice* v : _voices) {
if (v->addChord(chord))
return; // chord added to existing voice
}
// couldn't add chord to existing voice so create a new one
VirtualVoice* vv = new VirtualVoice(chord);
_voices.push_back(vv);
}

bool VirtualVoice::addChord(RawChord* chord) {
for (RawChord* rc : _chords) {
// try to combine chord with an existing chord in the voice
if (rc->addNotesFromChord(chord))
return true; // chords coincide (same start and end tick) so were combined
if (rc->overlaps(chord))
return false; // chords overlap so can't go in this voice
}
// Chord neither coincides nor overlaps with any chords in voice, so it cannot be combined
// with an exiting chord, but it can still be added to the voice as a separate chord.
_chords.push_back(chord);
return true;
}

//---------------------------------------------------------
// addNotesFromChord
//---------------------------------------------------------

bool RawChord::addNotesFromChord(RawChord* chord)
{
if (!coincides(chord))
return false;
for (RawNote* rn : chord->notes())
_notes.push_back(rn);
return true;
}

//---------------------------------------------------------
// sortVoicesByAveragePitch
//---------------------------------------------------------

void VirtualMeasure::sortVoicesByAveragePitch()
{
qDebug("Sort Voices");
int n = numVoices();
if (n < 2)
return;

// sort voices by average pitch, highest first
std::sort(_voices.begin(), _voices.end(),
[] (VirtualVoice* v1, VirtualVoice* v2) { return v1->averagePitch() > v2->averagePitch(); });

// In MuseScore odd voices are higher than even voices (Voice 1 > Voice 3 > Voice 4 > Voice 2) so now
// sort into order: highest, lowest, 2nd highest, 2nd lowest, etc (can have more than 4 virtual voices).
for (int i = 1; i < n; i += 2) {
// move the final element (the lowest pitch
// note not yet moved) up to the i-th place
VirtualVoice* v = _voices[n-1];
_voices.pop_back();
_voices.insert(_voices.begin()+i, v);
}
}

//---------------------------------------------------------
// sortChordsByTick
//---------------------------------------------------------

void VirtualVoice::sortChordsByTick()
{
// sort chords by tick, lowest first
std::sort(_chords.begin(), _chords.end(),
[] (RawChord* rc1, RawChord* rc2) { return rc1->tick() < rc2->tick(); });
}

//---------------------------------------------------------
// sortNotesByPitch
//---------------------------------------------------------

void RawChord::sortNotesByPitch()
{
// sort notes by pitch, highest first
std::sort(_notes.begin(), _notes.end(),
[] (RawNote* rn1, RawNote* rn2) { return rn1->pitch() > rn2->pitch(); });
}

//---------------------------------------------------------
// numNotes
//---------------------------------------------------------

int VirtualVoice::numNotes() const
{
int sum = 0;
for (RawChord* rc : _chords)
sum += rc->numNotes();
return sum;
}

//---------------------------------------------------------
// sumPitches
//---------------------------------------------------------

int VirtualVoice::sumPitches() const
{
int sum = 0;
for (RawChord* rc : _chords)
sum += rc->sumPitches();
return sum;
}

int RawChord::sumPitches() const
{
int sum = 0;
for (RawNote* rn : _notes)
sum += rn->pitch();
return sum;
}

#if 0
//---------------------------------------------------------
// print
//---------------------------------------------------------

void VirtualMeasure::print()
{
int i = 0;
for (VirtualVoice* v : _voices) {
qDebug("~VirtualVoice %i", i);
v->print();
i++;
}
}

void VirtualVoice::print()
{
int i = 0;
for (RawChord* rc : _chords) {
qDebug("~~VirtualVoiceChord %i", i);
rc->print();
i++;
}
}

void RawChord::print()
{
qDebug("~~~RawChord: tick = %i, ticks = %i (endTick = %i)", tick(), ticks(), endTick());
for (RawNote* rn : _notes)
rn->print();
}
#endif

} // namespace Ms

0 comments on commit 1fbbfb3

Please sign in to comment.