Skip to content

Commit

Permalink
Merge pull request #25922 from alexpavlov96/guitarbend_import_part_2
Browse files Browse the repository at this point in the history
guitar bend import: part 2 - treating separate notes of chord
  • Loading branch information
alexpavlov96 authored Dec 27, 2024
2 parents 4c05925 + bd673e8 commit d0e1b79
Show file tree
Hide file tree
Showing 9 changed files with 828 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ BendDataCollector::ImportedBendInfo fillBendInfo(const Note* note, const PitchVa
void BendDataCollector::storeBendData(mu::engraving::Note* note, const mu::engraving::PitchValues& pitchValues)
{
if (!pitchValues.empty()) {
m_bendInfoForNote[note->track()][note->tick().ticks()] = fillBendInfo(note, pitchValues);
m_bendInfoForNote[note->track()][note->tick().ticks()][note->pitch()] = fillBendInfo(note, pitchValues);
}
}

Expand All @@ -47,8 +47,10 @@ BendDataContext BendDataCollector::collectBendDataContext()
BendDataContext bendDataCtx;

for (const auto& [track, trackInfo] : m_bendInfoForNote) {
for (const auto& [tick, importedBendInfo] : trackInfo) {
fillBendDataContextForNote(bendDataCtx, importedBendInfo);
for (const auto& [tick, tickInfo] : trackInfo) {
for (const auto& [pitch, importedBendInfo] : tickInfo) {
fillBendDataContextForNote(bendDataCtx, importedBendInfo);
}
}
}

Expand Down Expand Up @@ -126,27 +128,8 @@ std::vector<BendDataCollector::BendSegment> bendSegmentsFromPitchValues(const Pi

BendDataCollector::ImportedBendInfo fillBendInfo(const Note* note, const PitchValues& pitchValues)
{
PitchValues adaptedPitchValues;
adaptedPitchValues.reserve(pitchValues.size() + 2);

if (pitchValues.front().time != 0) {
PitchValue firstPv = pitchValues.front();
firstPv.time = 0;
adaptedPitchValues.push_back(firstPv);
}

for (const auto& pv : pitchValues) {
adaptedPitchValues.push_back(pv);
}

if (pitchValues.back().time != BEND_DIVISIONS) {
PitchValue lastPv = pitchValues.back();
lastPv.time = BEND_DIVISIONS;
adaptedPitchValues.push_back(lastPv);
}

BendDataCollector::ImportedBendInfo info;
info.segments = bendSegmentsFromPitchValues(adaptedPitchValues, note->tieBack());
info.segments = bendSegmentsFromPitchValues(pitchValues, note->tieBack());
info.note = note;

for (const auto& bs : info.segments) {
Expand All @@ -160,7 +143,7 @@ BendDataCollector::ImportedBendInfo fillBendInfo(const Note* note, const PitchVa

static bool isSlightBend(const BendDataCollector::ImportedBendInfo& importedInfo)
{
if (importedInfo.pitchChangesAmount != 1 || importedInfo.note->tieFor() || importedInfo.segments.size() != 2) {
if (importedInfo.pitchChangesAmount != 1 || importedInfo.note->tieFor()) {
return false;
}

Expand All @@ -185,18 +168,21 @@ void fillSlightBendDataContext(BendDataContext& bendDataCtx, const BendDataColle

bendDataCtx.bendChordDurations[note->track()][tick.ticks()] = std::move(bendDurations);

BendDataContext::BendData slightBendData;
slightBendData.quarterTones = 1;
BendDataContext::BendChordData& slightBendChordData = bendDataCtx.bendDataByEndTick[note->track()][tick.ticks()];
slightBendChordData.startTick = tick;

BendDataContext::BendNoteData slightBendNoteData;
slightBendNoteData.quarterTones = 1;

const auto& firstSeg = importedInfo.segments.front();
if (firstSeg.middleTime != -1) {
slightBendData.startFactor = (double)firstSeg.middleTime / BEND_DIVISIONS;
slightBendNoteData.startFactor = (double)firstSeg.middleTime / BEND_DIVISIONS;
}

slightBendData.endFactor = (double)(firstSeg.endTime + 1) / BEND_DIVISIONS;
slightBendData.type = GuitarBendType::SLIGHT_BEND;
slightBendData.startTick = tick;
bendDataCtx.bendDataByEndTick[note->track()][tick.ticks()] = std::move(slightBendData);
slightBendNoteData.endFactor = (double)(firstSeg.endTime + 1) / BEND_DIVISIONS;
slightBendNoteData.type = GuitarBendType::SLIGHT_BEND;

slightBendChordData.noteDataByPitch[note->pitch()] = std::move(slightBendNoteData);
}

static bool isFirstPrebend(const BendDataCollector::ImportedBendInfo& importedInfo)
Expand All @@ -208,14 +194,17 @@ static bool isFirstPrebend(const BendDataCollector::ImportedBendInfo& importedIn
static void fillPrebendDataContext(BendDataContext& bendDataCtx, const BendDataCollector::ImportedBendInfo& importedInfo)
{
const Note* note = importedInfo.note;
BendDataContext::BendData prebendData;
Fraction tick = note->tick();
BendDataContext::BendChordData& prebendChordData = bendDataCtx.bendDataByEndTick[note->track()][tick.ticks()];
prebendChordData.startTick = tick;

const auto& firstSeg = importedInfo.segments.front();
prebendData.type = GuitarBendType::PRE_BEND;
prebendData.startTick = note->tick();
prebendData.quarterTones = firstSeg.pitchDiff / 25;

bendDataCtx.bendDataByEndTick[note->track()][note->tick().ticks()] = std::move(prebendData);
BendDataContext::BendNoteData prebendNoteData;
prebendNoteData.type = GuitarBendType::PRE_BEND;
prebendNoteData.quarterTones = firstSeg.pitchDiff / 25;

prebendChordData.noteDataByPitch[note->pitch()] = std::move(prebendNoteData);

std::vector<Fraction> bendDurations;
Fraction duration = note->chord()->actualTicks();
Expand All @@ -228,19 +217,25 @@ static void fillNormalBendDataContext(BendDataContext& bendDataCtx, const BendDa
size_t startIndex)
{
// TODO: fill chords durations in proportion to bend diagram
const Note* note = importedInfo.note;
BendDataContext::BendData bendData;

if (startIndex >= importedInfo.segments.size() - 1) {
if (startIndex >= importedInfo.segments.size()) {
return;
}

const auto& firstSeg = importedInfo.segments.at(startIndex);
bendData.type = GuitarBendType::BEND;
bendData.startTick = note->tick();
bendData.quarterTones = firstSeg.pitchDiff / 25;
if (firstSeg.pitchDiff == 0) {
return;
}

const Note* note = importedInfo.note;
Fraction tick = note->tick();
BendDataContext::BendChordData& bendChordData = bendDataCtx.bendDataByEndTick[note->track()][note->chord()->actualTicks().ticks()];
bendChordData.startTick = tick;

BendDataContext::BendNoteData bendNoteData;
bendNoteData.type = GuitarBendType::BEND;
bendNoteData.quarterTones = firstSeg.pitchDiff / 25;

bendDataCtx.bendDataByEndTick[note->track()][note->chord()->actualTicks().ticks()] = std::move(bendData);
bendChordData.noteDataByPitch[note->pitch()] = std::move(bendNoteData);

std::vector<Fraction> bendDurations;
Fraction duration = note->chord()->actualTicks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class BendDataCollector
};

private:
// todo: here storing 1 note for "track+tick". TODO: adapt for chord
std::unordered_map<mu::engraving::track_idx_t, std::map<int, ImportedBendInfo> > m_bendInfoForNote;
std::unordered_map<mu::engraving::track_idx_t, std::map<int, std::unordered_map<int /*pitch*/, ImportedBendInfo> > > m_bendInfoForNote;
};
} // mu::iex::guitarpro
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@ class Chord;

namespace mu::iex::guitarpro {
struct BendDataContext {
struct BendData {
mu::engraving::Fraction startTick;
struct BendNoteData {
double startFactor = 0.0;
double endFactor = 1.0;
int quarterTones = 0;
mu::engraving::GuitarBendType type = mu::engraving::GuitarBendType::BEND;
};

struct BendChordData {
mu::engraving::Fraction startTick;
std::map<int /* pitch */, BendNoteData> noteDataByPitch;
};

std::unordered_map<mu::engraving::track_idx_t, std::map<int, std::vector<mu::engraving::Fraction> > > bendChordDurations;
std::unordered_map<mu::engraving::track_idx_t, std::map<int, BendData> > bendDataByEndTick;
std::unordered_map<mu::engraving::track_idx_t, std::map<int, BendChordData> > bendDataByEndTick;
};
} // mu::iex::guitarpro
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
using namespace mu::engraving;

namespace mu::iex::guitarpro {
static void createGuitarBends(const BendDataContext& bendDataCtx, mu::engraving::Note* note);
static void createGuitarBends(const BendDataContext& bendDataCtx, const mu::engraving::Chord* chord);

BendDataProcessor::BendDataProcessor(mu::engraving::Score* score)
: m_score(score)
Expand Down Expand Up @@ -65,7 +65,7 @@ void BendDataProcessor::processBends(const BendDataContext& bendDataCtx)
}

Fraction currentTick = mainChord->tick() + mainChord->ticks();
createGuitarBends(bendDataCtx, mainChord->upNote());
createGuitarBends(bendDataCtx, mainChord);

for (size_t i = 1; i < chordsDurations.size(); i++) {
const Measure* currentMeasure = m_score->tick2measure(currentTick);
Expand All @@ -87,8 +87,7 @@ void BendDataProcessor::processBends(const BendDataContext& bendDataCtx)
return;
}

Note* existingNote = existingChord->upNote(); // TODO: different notes
createGuitarBends(bendDataCtx, existingNote);
createGuitarBends(bendDataCtx, existingChord);
currentTick += existingChord->ticks();
} else {
// TODO: create new segment and new chord on it
Expand All @@ -99,13 +98,11 @@ void BendDataProcessor::processBends(const BendDataContext& bendDataCtx)
}
}

static void createGuitarBends(const BendDataContext& bendDataCtx, mu::engraving::Note* note)
static void createGuitarBends(const BendDataContext& bendDataCtx, const mu::engraving::Chord* chord)
{
Score* score = note->score();
const Chord* chord = toChord(note->parent());
Score* score = chord->score();
int chordTicks = chord->tick().ticks();

// TODO: distinguish notes from same chord
if (bendDataCtx.bendDataByEndTick.find(chord->track()) == bendDataCtx.bendDataByEndTick.end()) {
LOGE() << "bend import error: bends data on track " << chord->track() << " doesn't exist";
return;
Expand All @@ -117,79 +114,104 @@ static void createGuitarBends(const BendDataContext& bendDataCtx, mu::engraving:
return;
}

const BendDataContext::BendData& bendData = currentTrackData.at(chordTicks);
const Measure* startMeasure = score->tick2measure(bendData.startTick);
const BendDataContext::BendChordData& bendChordData = currentTrackData.at(chordTicks);
const Measure* startMeasure = score->tick2measure(bendChordData.startTick);

if (!startMeasure) {
LOGE() << "bend import error : no valid measure for track " << chord->track() << ", tick " << bendData.startTick.ticks();
LOGE() << "bend import error : no valid measure for track " << chord->track() << ", tick " << bendChordData.startTick.ticks();
return;
}

const Chord* startChord = startMeasure->findChord(bendData.startTick, chord->track());
const Chord* startChord = startMeasure->findChord(bendChordData.startTick, chord->track());

if (!startChord) {
LOGE() << "bend import error : no valid chord for track " << chord->track() << ", tick " << bendData.startTick.ticks();
LOGE() << "bend import error : no valid chord for track " << chord->track() << ", tick " << bendChordData.startTick.ticks();
return;
}

Note* startNote = startChord->upNote(); // TODO: separate notes in the same chord
int pitch = bendData.quarterTones / 2;

if (bendData.type == GuitarBendType::PRE_BEND) {
int pitch = bendData.quarterTones / 2;
note->setPitch(note->pitch() + pitch);
note->setTpcFromPitch();
GuitarBend* bend = score->addGuitarBend(bendData.type, note);
QuarterOffset quarterOff = bendData.quarterTones % 2 ? QuarterOffset::QUARTER_SHARP : QuarterOffset::NONE;
bend->setEndNotePitch(note->pitch(), quarterOff);
Note* startNote = bend->startNote();
if (startNote) {
startNote->setPitch(note->pitch() - pitch);
startNote->setTpcFromPitch();
}
} else if (bendData.type == GuitarBendType::SLIGHT_BEND) {
GuitarBend* bend = score->addGuitarBend(bendData.type, note);
bend->setStartTimeFactor(bendData.startFactor);
bend->setEndTimeFactor(bendData.endFactor);
} else {
if (startChord == chord) {
LOGE() << "bend import error : start and end chords are the same for track " << chord->track() << ", tick " <<
bendData.startTick.ticks();
return;
}
std::vector<Note*> startChordNotes = startChord->notes();
std::vector<Note*> endChordNotes = chord->notes();

IF_ASSERT_FAILED(startChordNotes.size() == endChordNotes.size()) {
LOGE() << "bend import error: start and end chord sizes don't match for track " << chord->track() << ", tick " <<
bendChordData.startTick.ticks();
return;
}

GuitarBend* bend = score->addGuitarBend(bendData.type, startNote, note);
if (!bend) {
LOGE() << "bend wasn't created for track " << chord->track() << ", tick " << startChord->tick().ticks();
return;
std::sort(startChordNotes.begin(), startChordNotes.end(), [](Note* l, Note* r) {
return l->pitch() < r->pitch();
});

std::sort(endChordNotes.begin(), endChordNotes.end(), [](Note* l, Note* r) {
return l->pitch() < r->pitch();
});

for (size_t noteIndex = 0; noteIndex < endChordNotes.size(); noteIndex++) {
Note* note = endChordNotes[noteIndex];
if (bendChordData.noteDataByPitch.find(note->pitch()) == bendChordData.noteDataByPitch.end()) {
continue;
}

QuarterOffset quarterOff = bendData.quarterTones % 2 ? QuarterOffset::QUARTER_SHARP : QuarterOffset::NONE;
bend->setEndNotePitch(startNote->pitch() + pitch, quarterOff);
bend->setStartTimeFactor(bendData.startFactor);
bend->setEndTimeFactor(bendData.endFactor);
}
const BendDataContext::BendNoteData& bendNoteData = bendChordData.noteDataByPitch.at(note->pitch());
Note* startNote = startChordNotes[noteIndex];
int pitch = bendNoteData.quarterTones / 2;

if (bendNoteData.type == GuitarBendType::PRE_BEND) {
int pitch = bendNoteData.quarterTones / 2;
note->setPitch(note->pitch() + pitch);
note->setTpcFromPitch();
GuitarBend* bend = score->addGuitarBend(bendNoteData.type, note);
QuarterOffset quarterOff = bendNoteData.quarterTones % 2 ? QuarterOffset::QUARTER_SHARP : QuarterOffset::NONE;
bend->setEndNotePitch(note->pitch(), quarterOff);
Note* startNote = bend->startNote();
if (startNote) {
startNote->setPitch(note->pitch() - pitch);
startNote->setTpcFromPitch();
}
} else if (bendNoteData.type == GuitarBendType::SLIGHT_BEND) {
GuitarBend* bend = score->addGuitarBend(bendNoteData.type, note);
bend->setStartTimeFactor(bendNoteData.startFactor);
bend->setEndTimeFactor(bendNoteData.endFactor);
} else {
if (startChord == chord) {
LOGE() << "bend import error : start and end chords are the same for track " << chord->track() << ", tick " <<
bendChordData.startTick.ticks();
return;
}

int newPitch = note->pitch();
Note* tiedNote = nullptr;
GuitarBend* bend = score->addGuitarBend(bendNoteData.type, startNote, note);
if (!bend) {
LOGE() << "bend wasn't created for track " << chord->track() << ", tick " << startChord->tick().ticks();
return;
}

Tie* tieFor = startNote->tieFor();
if (tieFor) {
tiedNote = tieFor->endNote();
if (bendData.type != GuitarBendType::PRE_BEND) {
startNote->remove(tieFor);
QuarterOffset quarterOff = bendNoteData.quarterTones % 2 ? QuarterOffset::QUARTER_SHARP : QuarterOffset::NONE;
bend->setEndNotePitch(startNote->pitch() + pitch, quarterOff);
bend->setStartTimeFactor(bendNoteData.startFactor);
bend->setEndTimeFactor(bendNoteData.endFactor);
}
}

while (tiedNote) {
tiedNote->setPitch(newPitch);
tiedNote->setTpcFromPitch();
Tie* tie = tiedNote->tieFor();
if (!tie) {
break;
int newPitch = note->pitch();
Note* tiedNote = nullptr;

Tie* tieFor = startNote->tieFor();
if (tieFor) {
tiedNote = tieFor->endNote();
if (bendNoteData.type != GuitarBendType::PRE_BEND) {
startNote->remove(tieFor);
}
}

tiedNote = tie->endNote();
while (tiedNote) {
tiedNote->setPitch(newPitch);
tiedNote->setTpcFromPitch();
Tie* tie = tiedNote->tieFor();
if (!tie) {
break;
}

tiedNote = tie->endNote();
}
}
}
} // namespace mu::iex::guitarpro
Loading

0 comments on commit d0e1b79

Please sign in to comment.