Skip to content

Commit

Permalink
New voicing algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
shoogle committed Aug 22, 2016
1 parent 6ff8dc4 commit 25ca630
Show file tree
Hide file tree
Showing 9 changed files with 475 additions and 290 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
152 changes: 79 additions & 73 deletions 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 @@ -1460,28 +1461,26 @@ void Score::repitchNote(const Position& p, bool replace)
// separateVoices
//---------------------------------------------------------

void Score::separateVoices(int max_voices)
void Score::recalculateVoices(int startTick, int endTick, int staffIdx)
{
Q_ASSERT(max_voices <= VOICES);
// Voice separation is a 4 step process for each measure:
// 1) Look for overlapping notes and send them to higher "virtual voices".
// 2) Simplify the durations of notes in all virtual voices. This
// replaces tied notes with long notes, but some ties might remain.
// 3) Remaining ties might allow some virtual voices to be combined, so do it.
// 4) Re-render as real voices, dropping voices greater than max_voices.
// Some steps use MIDI-style note on/off timings instead of the usual representation.

VirtualVoiceManager vvm;
// store InputState location so we can get back to it later.
Segment* inputSegment = _is.segment();
int inputTrack = _is.track();
for (Segment* seg = _is.cr()->measure()->prevMeasure()->first(Segment::Type::ChordRest); seg; seg = seg->next(Segment::Type::ChordRest)) {
ChordRest* curr = seg->cr(_is.track());
if (curr->isChord())
qDebug("Chord!");
if (curr->isChord()) {
// combine tied chords
Chord* chord = static_cast<Chord*>(curr); // chord at segment
foreach (Note* n, chord->notes()) {
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())
Expand All @@ -1491,74 +1490,81 @@ void Score::separateVoices(int max_voices)
Note* nn = n;
for (t = n->tieFor(); t; t = nn->tieFor()) {
nn = t->endNote();
if (nn->chord()->measure() == chord->measure()) {
if (nn->tick() + nn->chord()->duration().ticks() <= maxTick) {
ticks += nn->chord()->duration().ticks();
}
else {
tnf = nn;
break;
}
}
vvm.addChord(new RawChord(n->tick(), ticks, new RawNote(n->pitch(), tnf, tnb)));
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
}
}
}
//qDebug("##SIMPLIFY!##");
//vvm.print();
vvm.sortVoices(inputTrack % 2 == 0);
int voice = 0;
QLinkedListIterator<VirtualVoice*> vvit(*vvm.voices());
while (vvit.hasNext()) {
VirtualVoice* vvoice = vvit.next();
int track = inputTrack + voice;
Segment* seg = inputSegment->measure()->prevMeasure()->first(Segment::Type::ChordRest);
QLinkedListIterator<RawChord*> rcit(*vvoice->chords());
while (rcit.hasNext()) {
RawChord* rchord = rcit.next();
while (seg->tick() < rchord->tick()) {
if (seg->rtick() == 0 || seg->cr(track))
seg = setNoteRest(seg, track, NoteVal(), Fraction::fromTicks(rchord->tick() - seg->tick()), Direction::AUTO);
seg = seg->next(Segment::Type::ChordRest);
}
seg = setNoteRest(seg, track, NoteVal(0), Fraction::fromTicks(rchord->ticks()), Direction::AUTO);
Chord* newChord = static_cast<Chord*>(seg->cr(track));
Note* n = newChord->notes().front();
undoRemoveElement(n);
QLinkedListIterator<RawNote*> rnit(*rchord->notes());
while (rnit.hasNext()) {
RawNote* rnote = rnit.next();
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);
}
// 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);
while (seg->tick() < rchord->tick()) // might encounter segments from other voices before the one we want
seg = seg->next(Segment::Type::ChordRest);
}
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);
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);
}
while (seg && seg->tick() < rchord->endTick())
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);
}
if (seg)
seg = setNoteRest(seg, track, NoteVal(), Fraction::fromTicks(inputSegment->tick() - seg->tick()), Direction::AUTO);
voice++;
if (voice >= VOICES)
break;
}
// now put the input state back where it was before
_is.setTrack(inputTrack);
_is.setSegment(inputSegment);
}
Expand Down

0 comments on commit 25ca630

Please sign in to comment.