refactoring of reading gp6 and gp7 files
This commit is contained in:
parent
1f7c8cddbf
commit
87bc70d6d3
5 changed files with 1 additions and 449 deletions
|
@ -44,8 +44,6 @@ set(MODULE_SRC
|
|||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gp7dombuilder.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gp67dombuilder.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gp67dombuilder.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gp67domfixer.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gp67domfixer.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gpaudiotrack.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gpaudiotrack.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/gtp/gpbar.cpp
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include "global/log.h"
|
||||
|
||||
#include "gp67domfixer.h"
|
||||
|
||||
namespace Ms {
|
||||
GP67DomBuilder::GP67DomBuilder()
|
||||
|
@ -66,8 +65,6 @@ void GP67DomBuilder::buildGPDomModel(QDomElement* qdomElem)
|
|||
buildGPMasterTracks(&masterTrack);
|
||||
buildGPAudioTracks(&eachTrack);
|
||||
buildGPTracks(&eachTrack);
|
||||
|
||||
GP67DomFixer::fixGPDomModel(_gpDom.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<GPDomModel> GP67DomBuilder::getGPDomModel()
|
||||
|
|
|
@ -1,386 +0,0 @@
|
|||
#include "gp67domfixer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
//#include "global/json_utils.hpp"
|
||||
#include "global/log.h"
|
||||
|
||||
namespace {
|
||||
static constexpr int MIN_NOTES{ 3 };
|
||||
|
||||
struct Dynamic {
|
||||
std::string name;
|
||||
std::string shortName;
|
||||
std::vector<int> degrees;
|
||||
};
|
||||
|
||||
static const std::vector<Dynamic> AvailableChords = {
|
||||
{ /*name:*/ "major", /*shortName:*/ " ", /*degrees:*/ { 0, 4, 7 } },
|
||||
{ /*name:*/ "minor", /*shortName:*/ "m", /*degrees:*/ { 0, 3, 7 } },
|
||||
{ /*name:*/ "dim", /*shortName:*/ "", /*degrees:*/ { 0, 3, 6 } },
|
||||
{ /*name:*/ "aug", /*shortName:*/ "", /*degrees:*/ { 0, 4, 8 } },
|
||||
{ /*name:*/ "2", /*shortName:*/ "", /*degrees:*/ { 0, 2, 4, 7 } },
|
||||
|
||||
{ /*name:*/ "7", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 10 } },
|
||||
{ /*name:*/ "m7", /*shortName:*/ "", /*degrees:*/ { 0, 3, 7, 10 } },
|
||||
{ /*name:*/ "maj7", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 11 } },
|
||||
{ /*name:*/ "dim7", /*shortName:*/ "", /*degrees:*/ { 0, 3, 6, 9 } },
|
||||
{ /*name:*/ "m/maj7", /*shortName:*/ "", /*degrees:*/ { 0, 3, 7, 11 } },
|
||||
|
||||
{ /*name:*/ "7+5", /*shortName:*/ "", /*degrees:*/ { 0, 4, 8, 10 } },
|
||||
{ /*name:*/ "7sus2", /*shortName:*/ "", /*degrees:*/ { 0, 2, 7, 10 } },
|
||||
{ /*name:*/ "7sus4", /*shortName:*/ "", /*degrees:*/ { 0, 5, 7, 10 } },
|
||||
{ /*name:*/ "6", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 9 } },
|
||||
{ /*name:*/ "m6", /*shortName:*/ "", /*degrees:*/ { 0, 3, 7, 9 } },
|
||||
|
||||
{ /*name:*/ "9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 10, 2 } },
|
||||
{ /*name:*/ "9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 10, 2 } },
|
||||
|
||||
{ /*name:*/ "-9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 10, 1 } },
|
||||
{ /*name:*/ "-9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 10, 1 } },
|
||||
|
||||
{ /*name:*/ "m9", /*shortName:*/ "", /*degrees:*/ { 0, 3, 7, 10, 2 } },
|
||||
{ /*name:*/ "m9", /*shortName:*/ "", /*degrees:*/ { 0, 3, 10, 2 } },
|
||||
|
||||
{ /*name:*/ "m-9", /*shortName:*/ "", /*degrees:*/ { 0, 3, 7, 10, 1 } },
|
||||
{ /*name:*/ "m-9", /*shortName:*/ "", /*degrees:*/ { 0, 3, 10, 1 } },
|
||||
|
||||
{ /*name:*/ "maj9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 11, 2 } },
|
||||
{ /*name:*/ "maj9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 11, 2 } },
|
||||
|
||||
{ /*name:*/ "sus2", /*shortName:*/ "", /*degrees:*/ { 0, 2, 7 } },
|
||||
{ /*name:*/ "sus4", /*shortName:*/ "", /*degrees:*/ { 0, 5, 7 } },
|
||||
{ /*name:*/ "add9", /*shortName:*/ "", /*degrees:*/ { 0, 4, 7, 2 } },
|
||||
|
||||
{ /*name:*/ "5", /*shortName:*/ "", /*degrees:*/ { 0, 7 } }
|
||||
};
|
||||
|
||||
static const std::vector<Dynamic> AvailableChordsWithUnimportantOrder = {
|
||||
{ /*name:*/ "dim7", /*shortName:*/ "", /*degrees:*/ { 0, 3, 6, 9 } }
|
||||
};
|
||||
|
||||
static const std::vector<std::string> NoteNames = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
|
||||
}
|
||||
|
||||
using namespace Ms;
|
||||
|
||||
GP67DomFixer::GP67DomFixer()
|
||||
{
|
||||
}
|
||||
|
||||
void GP67DomFixer::fixGPDomModel(GPDomModel* gpDom)
|
||||
{
|
||||
if (isLyricsOnBeats(gpDom)) {
|
||||
LOGD() << "lyrics on beats from gp";
|
||||
} else {
|
||||
LOGD() << "parse and self break lyrics on beats";
|
||||
breakLyricsOnBeats(gpDom);
|
||||
}
|
||||
|
||||
if (isHasDiagrams(gpDom)) {
|
||||
LOGD() << "diagrams read from gp";
|
||||
} else {
|
||||
LOGD() << "parse and self create diagrams";
|
||||
createDiagrams(gpDom);
|
||||
}
|
||||
}
|
||||
|
||||
bool GP67DomFixer::isLyricsOnBeats(const GPDomModel* gpDom)
|
||||
{
|
||||
const auto& masterBars = gpDom->masterBars();
|
||||
for (const auto& [trackNum, track] : gpDom->tracks()) {
|
||||
if (track->lyrics().empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t mi = 0; mi < masterBars.size(); ++mi) {
|
||||
const auto& mb = masterBars[mi];
|
||||
|
||||
IF_ASSERT_FAILED(mb->bars().size() > trackNum) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& trackBar = mb->bars().at(trackNum);
|
||||
if (trackBar->_voices.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& voice = trackBar->_voices[0];
|
||||
for (auto& beat : voice->_beats) {
|
||||
if (!beat->lyrics().empty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//! NOTE За основу взят код старого плеера от сюда
|
||||
//! https://git.wsmgroup.ru/web/haxe-flash_ultimateguitar/blob/master/TabReader/src/songModel/Song.hx#L992
|
||||
|
||||
void GP67DomFixer::breakLyricsOnBeats(GPDomModel* gpDom)
|
||||
{
|
||||
// std::vector<std::unique_ptr<GPMasterBar>>& masterBars = gpDom->_masterBars;
|
||||
//
|
||||
// for ( const auto& [trackNum, track] : gpDom->tracks()) {
|
||||
//
|
||||
// if (track->lyrics().empty()) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// std::string originTrackLyrics = track->lyrics();
|
||||
// size_t trackLyricsOffset = track->lyricsOffset();
|
||||
//
|
||||
// //! NOTE Подготовка и парсинг лирики
|
||||
// std::vector<std::string> beatLyrics;
|
||||
// {
|
||||
//
|
||||
// std::string trackLyrics;
|
||||
// trackLyrics.reserve(originTrackLyrics.size());
|
||||
//
|
||||
// bool skip = false;
|
||||
// for (auto c : originTrackLyrics) {
|
||||
// if (c == '[') {
|
||||
// skip = true;
|
||||
// } else if (c == ']') {
|
||||
// skip = false;
|
||||
// } else if (!skip) {
|
||||
// trackLyrics.push_back(c);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// xtz::replaceAll(trackLyrics, " -", "- ");
|
||||
// xtz::replaceAll(trackLyrics, "- ", "-");;
|
||||
//
|
||||
// // слова через дефис
|
||||
// xtz::replaceAll(trackLyrics, "--", "&&&&&-");
|
||||
// xtz::replaceAll(trackLyrics, "-", "- ");
|
||||
// xtz::replaceAll(trackLyrics, "\r", "_____");
|
||||
// xtz::replaceAll(trackLyrics, "\n", "_____");
|
||||
//
|
||||
// std::regex rx("_{5,}");
|
||||
// xtz::replaceAll(trackLyrics, rx, " ");
|
||||
//
|
||||
// xtz::split(trackLyrics, beatLyrics, " ");
|
||||
// for (auto &bl : beatLyrics) {
|
||||
// xtz::replaceAll(bl, "&&&&&", "-");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (beatLyrics.empty()) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// //! NOTE Установка лирики по битам
|
||||
// {
|
||||
// int numberOfBeatWithoutLyrics = 0;
|
||||
// int beatNumber = 0;
|
||||
//
|
||||
// for (size_t mi = 0; mi < masterBars.size(); ++mi) {
|
||||
//
|
||||
// auto& measure = masterBars[mi];
|
||||
//
|
||||
// IF_ASSERT_FAILED(measure->_bars.size() > trackNum) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// auto& trackBar = measure->_bars[trackNum];
|
||||
// if (trackBar->_voices.empty()) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// auto& voice = trackBar->_voices[0];
|
||||
// for ( auto& beat : voice->_beats) {
|
||||
//
|
||||
// bool hasLyrics = false;
|
||||
// if (mi < trackLyricsOffset) {
|
||||
// numberOfBeatWithoutLyrics++;
|
||||
// beatNumber++;
|
||||
// }
|
||||
// else {
|
||||
// if (!beat->isRest()) {
|
||||
// hasLyrics = true;
|
||||
// beatNumber++;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (hasLyrics && mi >= trackLyricsOffset) {
|
||||
// size_t index = beatNumber - numberOfBeatWithoutLyrics - 1;
|
||||
// if (index < beatLyrics.size()) {
|
||||
//
|
||||
// std::string beatLyric = beatLyrics[index];
|
||||
// xtz::replace(beatLyric, "+", " ");
|
||||
//
|
||||
// beat->setLyrics(trackNum, (uint32_t)mi, beatLyric);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
bool GP67DomFixer::isHasDiagrams(const GPDomModel* gpDom)
|
||||
{
|
||||
for (const auto& [trackNum, track] : gpDom->tracks()) {
|
||||
if (!track->diagram().empty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//! NOTE За основу взят код старого плеера от сюда
|
||||
//! https://git.wsmgroup.ru/web/haxe-flash_ultimateguitar/blob/master/TabReader/src/songModel/filter/SongFilterTracksRenameByInstruments.hx
|
||||
|
||||
void GP67DomFixer::createDiagrams(GPDomModel* gpDom)
|
||||
{
|
||||
// std::vector<std::unique_ptr<GPMasterBar>>& masterBars = gpDom->_masterBars;
|
||||
//
|
||||
// auto trackDiagramId = [](GPTrack* t, const std::string& chordName) {
|
||||
// for (const auto& [id, d] : t->_diagrams) {
|
||||
// if (d.name == chordName) {
|
||||
// return id;
|
||||
// }
|
||||
// }
|
||||
// return -1;
|
||||
// };
|
||||
//
|
||||
// auto lastDiagramId = [](GPTrack* t) {
|
||||
// int lastId = -1;
|
||||
// for (auto it = t->_diagrams.cbegin(); it != t->_diagrams.cend(); ++it) {
|
||||
// if (it->first > lastId) {
|
||||
// lastId = it->first;
|
||||
// }
|
||||
// }
|
||||
// return lastId;
|
||||
// };
|
||||
//
|
||||
//
|
||||
// for (auto& [trackNum, track] : gpDom->_tracks) {
|
||||
//
|
||||
// if (!track->isGuitar()) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// std::vector<GPTrack::String> strings = track->strings();
|
||||
// for (size_t mi = 0; mi < masterBars.size(); ++mi) {
|
||||
//
|
||||
// auto& measure = masterBars[mi];
|
||||
//
|
||||
// IF_ASSERT(measure->_bars.size() > trackNum) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// auto& trackBar = measure->_bars[trackNum];
|
||||
// if (trackBar->_voices.empty()) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// auto& voice = trackBar->_voices[0];
|
||||
// for ( auto& beat : voice->_beats) {
|
||||
//
|
||||
// std::string chordName = chordNameForBeatWithStrings(beat.get(), strings);
|
||||
// if (chordName.empty()) {
|
||||
// beat->setDiagramIdx(trackNum, (int32_t)mi, -1);
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// int diaId = trackDiagramId(track.get(), chordName);
|
||||
// if (diaId == -1) {
|
||||
// diaId = lastDiagramId(track.get()) + 1;
|
||||
//
|
||||
// //! TODO Нужно добавить др свойства Diagram
|
||||
// //! если нужно будет рисовать сами диаграмы,
|
||||
// //! сейчас диаграмы используются только, чтобы показывать аккорды
|
||||
// GPTrack::Diagram dia;
|
||||
// dia.id = diaId;
|
||||
// dia.name = chordName;
|
||||
//
|
||||
// track->addDiagram({dia.id, dia});
|
||||
// }
|
||||
//
|
||||
// beat->setDiagramIdx(trackNum, (int32_t)mi, diaId);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
std::string GP67DomFixer::chordNameForBeatWithStrings(GPBeat* beat, const std::vector<GPTrack::String>& strings)
|
||||
{
|
||||
// std::string chordName;
|
||||
// const std::vector<std::shared_ptr<GPNote>>& notes = beat->notes();
|
||||
// std::vector<int> chordMidiIndexes;
|
||||
//
|
||||
// if (notes.size() < MIN_NOTES) {
|
||||
// return std::string();
|
||||
// }
|
||||
//
|
||||
// for (const auto& note : notes) {
|
||||
//
|
||||
// if(!(note->string() >= 0 && note->string() < strings.size())) {
|
||||
// return std::string();
|
||||
// }
|
||||
//
|
||||
// IF_ASSERT(note->fret() >= 0) {
|
||||
// return std::string();
|
||||
// }
|
||||
//
|
||||
// int noteMidiIndex = strings[note->string()].tunning + note->fret();
|
||||
// chordMidiIndexes.push_back(noteMidiIndex);
|
||||
// }
|
||||
//
|
||||
// std::sort(chordMidiIndexes.begin(), chordMidiIndexes.end());
|
||||
//
|
||||
// int lastEqualDegreeBaseNoteMidiIndex = -1;
|
||||
// int k = 0;
|
||||
// for (int baseNoteMidiIndex : chordMidiIndexes)
|
||||
// {
|
||||
// std::vector<int> chordDegrees;
|
||||
// int baseNoteDegree = baseNoteMidiIndex % 12;
|
||||
// for (int outherNoteMidiIndex : chordMidiIndexes)
|
||||
// {
|
||||
// int outherNoteDegree = outherNoteMidiIndex % 12;
|
||||
// int chordDegree = (12 - (baseNoteDegree - outherNoteDegree)) % 12;
|
||||
// chordDegrees.push_back(chordDegree);
|
||||
// }
|
||||
//
|
||||
// std::sort(chordDegrees.begin(), chordDegrees.end());
|
||||
// auto last = std::unique(chordDegrees.begin(), chordDegrees.end());
|
||||
// chordDegrees.erase(last, chordDegrees.end());
|
||||
//
|
||||
// const std::vector<Dynamic>* availableChords = nullptr;
|
||||
// if (k > 0)
|
||||
// {
|
||||
// availableChords = &AvailableChordsWithUnimportantOrder;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// availableChords = &AvailableChords;
|
||||
// }
|
||||
//
|
||||
// for (const Dynamic& availableChord : *availableChords)
|
||||
// {
|
||||
// std::vector<int> availableChordDegrees = availableChord.degrees;
|
||||
// std::sort(availableChordDegrees.begin(), availableChordDegrees.end());
|
||||
//
|
||||
// if (chordDegrees == availableChordDegrees) {
|
||||
// if (lastEqualDegreeBaseNoteMidiIndex == -1 || baseNoteMidiIndex < lastEqualDegreeBaseNoteMidiIndex) {
|
||||
// lastEqualDegreeBaseNoteMidiIndex = baseNoteMidiIndex;
|
||||
// IF_ASSERT(baseNoteDegree >= 0 && baseNoteDegree < NoteNames.size()) {
|
||||
// return std::string();
|
||||
// }
|
||||
// chordName = NoteNames[baseNoteDegree] + (availableChord.shortName.empty() ? availableChord.name : availableChord.shortName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// k++;
|
||||
// }
|
||||
//
|
||||
// return chordName;
|
||||
|
||||
return "";
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
#ifndef GP67DOMFIXER_HPP
|
||||
#define GP67DOMFIXER_HPP
|
||||
|
||||
#include "gpdommodel.h"
|
||||
|
||||
namespace Ms {
|
||||
//! NOTE Раннии версии файлов GP6 отличаются от текущих версий GP6
|
||||
//! Например в ранних версиях не записывалась лирика разбитая по битам
|
||||
//! - нужно самостоятельно её парсить и разбивать по битам
|
||||
//! Не записывалась информация о диаграммах (аккордах)
|
||||
//! Может чтото ещё обнаружится, этот класс для того,
|
||||
//! чтобы привести старые дом модели GP6 к текущей
|
||||
|
||||
class GP67DomFixer
|
||||
{
|
||||
public:
|
||||
GP67DomFixer();
|
||||
|
||||
static void fixGPDomModel(GPDomModel* gpDom);
|
||||
|
||||
private:
|
||||
|
||||
static bool isLyricsOnBeats(const GPDomModel* gpDom);
|
||||
static void breakLyricsOnBeats(GPDomModel* gpDom);
|
||||
|
||||
static bool isHasDiagrams(const GPDomModel* gpDom);
|
||||
static void createDiagrams(GPDomModel* gpDom);
|
||||
static std::string chordNameForBeatWithStrings(GPBeat* beat, const std::vector<GPTrack::String>& strings);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // GP67DOMFIXER_HPP
|
|
@ -1334,15 +1334,7 @@ bool GuitarPro1::read(QFile* fp)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*! Из GP HFret может приходить дробный. Вот что дробное значение означает
|
||||
* чтобы извлеч флажелет палец на струнне должен делить струнну не абы как,
|
||||
* а одна часть должна быть в кратно раз больше другой. Потому чтобы извлеч к примеру
|
||||
* флажелет с 3 лада надо прижать струну не сразу над ладом, а немного в стороне. GP показывает насколько
|
||||
* именно в стороне, так для флажелет на 3 ладу можно взять либо сместившиь на 0.2 лада в одну сторону по ладу
|
||||
* либо на 0.3 лада в другую сторону. Звук при этом извлекается разный, но насколько я знаю с точки зрения
|
||||
* стандартной нотной грамоты запись должна быть одинаковой - то есть так <3>
|
||||
* Теперь зная это, можно исправить объявленный ниже велосипед
|
||||
*/
|
||||
|
||||
int GuitarPro::harmonicOvertone(Note* note, float harmonicValue, int harmonicType)
|
||||
{
|
||||
int result{ 0 };
|
||||
|
@ -1368,23 +1360,6 @@ int GuitarPro::harmonicOvertone(Note* note, float harmonicValue, int harmonicTyp
|
|||
result = 40;
|
||||
}
|
||||
|
||||
//! Тот питч, который мы посчитали выше верен для натурального флажелета,
|
||||
//! чтобы получить правильный питч для искуственного флажелета
|
||||
//! то надо добавить добавить лад на котором взят флажелет. Насколько я понял семантика такова
|
||||
//! HFret показывает на каком ладу надо брать флажелет относительно другого лада, для искуственного флажелета
|
||||
//! этот референс это палец левой руки, который зажимает струну на ладу, для натурального левая рука ничего не зажимает, а только извлекает
|
||||
//! поэтому референс это открытая струна, то есть лад 0. На примере
|
||||
//! в гитар про стоит такая запись "<2.4>" это означает что надо взять флажелет примерно на третьем ладу, из GP файла
|
||||
//! к нам придет значение HFret 2.4 , высота звука при этом равна (высота открытой струны плюс 36)
|
||||
//! теперь в гитар про встречаем другую запись "0 <2.4>" физически она не имеет смысла, но означает
|
||||
//! возьми искуственный флажелет на 0 ладу с высотой звука 2.4. ИЗ GP файла HFret нам придет 2.4
|
||||
//! поэтому высота звука при этом равна (высота взука на ладе 0 плюс 36). То есть высота звука для этих двух примеров
|
||||
//! долдна быть одинаковая, что как GP и проигрывает.
|
||||
//! другой пример искуствунного флажелета но на этот раз на первом ладу
|
||||
//! встречаем такую запись "1 <3.4>" - казалось бы что то новенькое, значение 3.4 в вышерпиведенных наших if'ов нету
|
||||
//! но на самом деле из GP файла HFret нам как и ранее придет 2.4, то есть как и ранее harmonicFret мы определим 36,
|
||||
//! но теперь это смещение надо прибавить к высоте ноты на пером ладу.
|
||||
|
||||
return harmonicType == 1 ? result : (result + note->fret());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue