LCOV - code coverage report
Current view: top level - src/engraving/dom - stringdata.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 85.4 % 370 316
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "stringdata.h"
      24              : 
      25              : #include <map>
      26              : 
      27              : #include "defer.h"
      28              : 
      29              : #include "iengravingconfiguration.h" // IWYU pragma: keep
      30              : 
      31              : #include "chord.h"
      32              : #include "note.h"
      33              : #include "part.h"
      34              : #include "segment.h"
      35              : #include "staff.h"
      36              : 
      37              : #include "types/types.h"
      38              : 
      39              : using namespace mu;
      40              : 
      41              : namespace mu::engraving {
      42              : //---------------------------------------------------------
      43              : //   StringData
      44              : //---------------------------------------------------------
      45              : 
      46              : bool StringData::bFretting = false;
      47              : 
      48          562 : StringData::StringData(int numFrets, int numStrings, int strings[], bool useFlats)
      49              : {
      50          562 :     instrString strg = { 0, false, 0 };
      51          562 :     m_frets = numFrets;
      52              : 
      53          562 :     const int safeNumStrings = std::max(0, numStrings);
      54          562 :     m_stringTable.reserve(static_cast<size_t>(safeNumStrings));
      55         3916 :     for (int i = 0; i < safeNumStrings; i++) {
      56         3354 :         strg.pitch = strings[i];
      57         3354 :         strg.useFlat = useFlats;
      58         3354 :         m_stringTable.push_back(strg);
      59              :     }
      60          562 :     m_useFlats = useFlats;
      61          562 : }
      62              : 
      63            0 : StringData::StringData(int numFrets, std::vector<instrString>& strings)
      64              : {
      65            0 :     m_frets = numFrets;
      66              : 
      67            0 :     m_stringTable.clear();
      68            0 :     m_stringTable.reserve(strings.size());
      69            0 :     for (const instrString& i : strings) {
      70            0 :         m_stringTable.push_back(i);
      71              :     }
      72            0 : }
      73              : 
      74         1800 : bool StringData::isNull() const
      75              : {
      76         1800 :     return *this == StringData();
      77              : }
      78              : 
      79              : // called from import (musicxml/guitarpro/...)
      80         1397 : void StringData::set(const StringData& src)
      81              : {
      82         1397 :     *this = src;
      83         1397 :     if (isFiveStringBanjo()) {
      84            1 :         configBanjo5thString();
      85              :     }
      86         1397 : }
      87              : 
      88              : //---------------------------------------------------------
      89              : //   convertPitch
      90              : //   Finds string and fret for a note.
      91              : //
      92              : //   Fills *string and *fret with suitable values for pitch at given staff,
      93              : //   using the highest possible string.
      94              : //   If note cannot be fretted, uses fret 0 on nearest string and returns false
      95              : //
      96              : //    Note: Strings are stored internally from lowest (0) to highest (strings()-1),
      97              : //          but the returned *string value references strings in reversed, 'visual', order:
      98              : //          from highest (0) to lowest (strings()-1)
      99              : //---------------------------------------------------------
     100              : 
     101           50 : bool StringData::convertPitch(int pitch, const Staff* staff, int* string, int* fret) const
     102              : {
     103           50 :     return convertPitch(pitch, pitchOffsetAt(staff), string, fret);
     104           50 : }
     105              : 
     106              : //---------------------------------------------------------
     107              : //   convertPitch
     108              : //   Finds string and fret for a note.
     109              : //
     110              : //   Fills *string and *fret with suitable values for pitch at given tick of given staff,
     111              : //   using the highest possible string.
     112              : //   If note cannot be fretted, uses fret 0 on nearest string and returns false
     113              : //
     114              : //    Note: Strings are stored internally from lowest (0) to highest (strings()-1),
     115              : //          but the returned *string value references strings in reversed, 'visual', order:
     116              : //          from highest (0) to lowest (strings()-1)
     117              : //---------------------------------------------------------
     118              : 
     119           43 : bool StringData::convertPitch(int pitch, const Staff* staff, const Fraction& tick, int* string, int* fret) const
     120              : {
     121           43 :     return convertPitch(pitch, pitchOffsetAt(staff, tick), string, fret);
     122           43 : }
     123              : 
     124              : //---------------------------------------------------------
     125              : //   getPitch
     126              : //    Returns the pitch corresponding to the string / fret combination
     127              : //    at given staff.
     128              : //    Returns INVALID_PITCH if not possible
     129              : //    Note: frets above max fret are accepted.
     130              : //---------------------------------------------------------
     131              : 
     132          900 : int StringData::getPitch(int string, int fret, const Staff* staff) const
     133              : {
     134          900 :     return getPitch(string, fret, pitchOffsetAt(staff));
     135              : }
     136              : 
     137              : //---------------------------------------------------------
     138              : //   getPitch
     139              : //    Returns the pitch corresponding to the string / fret combination
     140              : //    at given tick of given staff.
     141              : //    Returns INVALID_PITCH if not possible
     142              : //    Note: frets above max fret are accepted.
     143              : //---------------------------------------------------------
     144              : 
     145         1219 : int StringData::getPitch(int string, int fret, const Staff* staff, const Fraction& tick) const
     146              : {
     147         1219 :     const CapoParams& capo = staff->capo(tick);
     148         1219 :     bool ignoredString = capo.active && muse::contains(capo.ignoredStrings, (string_idx_t)string);
     149         1219 :     return ignoredString ? getPitch(string, fret, pitchOffsetAt(staff)) : getPitch(string, fret, pitchOffsetAt(staff, tick));
     150              : }
     151              : 
     152              : //---------------------------------------------------------
     153              : //   fret
     154              : //    Returns the fret corresponding to the pitch / string combination
     155              : //    at given staff.
     156              : //    Returns INVALID_FRET_INDEX if not possible
     157              : //---------------------------------------------------------
     158              : 
     159          244 : int StringData::fret(int pitch, int string, const Staff* staff) const
     160              : {
     161          244 :     return fret(pitch, string, pitchOffsetAt(staff));
     162              : }
     163              : 
     164            2 : int StringData::fret(int pitch, int string, const Staff* staff, const Fraction& tick) const
     165              : {
     166            2 :     return fret(pitch, string, pitchOffsetAt(staff, tick));
     167              : }
     168              : 
     169              : //---------------------------------------------------------
     170              : //   fretChords
     171              : //    Assigns fretting to all the notes of each chord in the same segment of chord
     172              : //    re-using existing fretting wherever possible
     173              : //
     174              : //    Minimizes fret conflicts (multiple notes on the same string)
     175              : //    but marks as fretConflict notes which cannot be fretted
     176              : //    (outside tablature range) or which cannot be assigned
     177              : //    a separate string
     178              : //---------------------------------------------------------
     179              : 
     180         1486 : void StringData::fretChords(Chord* chord) const
     181              : {
     182         1486 :     if (bFretting) {
     183            0 :         return;
     184              :     }
     185         1486 :     bFretting = true;
     186         1486 :     DEFER {
     187         1486 :         bFretting = false;
     188         1486 :     };
     189              : 
     190         1486 :     int strings = static_cast<int>(this->strings());
     191         1486 :     const bool skipDeadNotes = chord->configuration()->keepDeadNotesUnchangedOnTranspose();
     192              : 
     193              :     // we need the notes sorted in order of string (from highest to lowest) and then pitch
     194         1486 :     std::map<int, Note*> sortedNotes;
     195         1486 :     int count = 0;
     196              :     // if chord parent is not a segment, the chord is special (usually a grace chord):
     197              :     // fret it by itself, ignoring the segment
     198         1486 :     if (!chord->ownershipParent()->isSegment()) {
     199           60 :         sortChordNotes(sortedNotes, chord, &count);
     200              :     } else {
     201              :         // scan each chord of seg from same staff as 'chord', inserting each of its notes in sortedNotes
     202         1426 :         Segment* seg = chord->segment();
     203              :         track_idx_t trk;
     204         1426 :         track_idx_t trkFrom = (chord->track() / VOICES) * VOICES;
     205         1426 :         track_idx_t trkTo   = trkFrom + VOICES;
     206         7130 :         for (trk = trkFrom; trk < trkTo; ++trk) {
     207         5704 :             EngravingItem* ch = seg->elist().at(trk);
     208         5704 :             if (ch && ch->isChord()) {
     209         1626 :                 sortChordNotes(sortedNotes, toChord(ch), &count);
     210              :             }
     211              :         }
     212              :     }
     213              : 
     214         3530 :     for (const auto& p : sortedNotes) {
     215         2044 :         Note* note = p.second;
     216         2044 :         if (note->string() >= strings) {
     217            0 :             note->undoChangeProperty(Pid::STRING, INVALID_STRING_INDEX);
     218            0 :             note->undoChangeProperty(Pid::FRET, INVALID_FRET_INDEX);
     219              :         }
     220              :     }
     221              : 
     222         1486 :     if (!strings) {
     223            0 :         return;
     224              :     }
     225              : 
     226              :     // we need to keep track of string allocation
     227         1486 :     std::vector<int> bUsed(strings);
     228              : 
     229              :     // determine used range of frets
     230         1486 :     int minFret = INT32_MAX;
     231         1486 :     int maxFret = INT32_MIN;
     232         3530 :     for (const auto& p : sortedNotes) {
     233         2044 :         Note* note = p.second;
     234         2044 :         if (note->string() != INVALID_STRING_INDEX && note->displayFret() == Note::DisplayFretOption::NoHarmonic) {
     235         1977 :             bUsed[note->string()]++;
     236              :         }
     237         2044 :         if (note->fret() != INVALID_FRET_INDEX && note->fret() < minFret) {
     238         1471 :             minFret = note->fret();
     239              :         }
     240         2044 :         if (note->fret() != INVALID_FRET_INDEX && note->fret() > maxFret) {
     241         1589 :             maxFret = note->fret();
     242              :         }
     243              :     }
     244              : 
     245              :     // scan chord notes from highest, matching with strings from the highest
     246         3530 :     for (const auto& p : sortedNotes) {
     247         2044 :         Note* note = p.second;
     248              :         int nString, nNewString;
     249              :         int nFret,   nNewFret,   nTempFret;
     250              : 
     251         2044 :         nString = nNewString = note->string();
     252         2044 :         nFret   = nNewFret   = note->fret();
     253         2044 :         note->setFretConflict(false);           // assume no conflicts on this note
     254         2044 :         if (skipDeadNotes && note->deadNote()) {
     255           25 :             continue;
     256              :         }
     257              :         // if no fretting (any invalid fretting has been erased by sortChordNotes() )
     258         2032 :         if (nString == INVALID_STRING_INDEX /*|| nFret == INVALID_FRET_INDEX || getPitch(nString, nFret) != note->pitch()*/) {
     259           67 :             const CapoParams& capo = note->staff()->capo(note->tick());
     260              :             // get a new fretting
     261           67 :             if (convertPitch(note->pitch(), pitchOffsetAt(chord->staff(), chord->tick()), &nNewString, &nNewFret,
     262           54 :                              capo) && note->displayFret()
     263          121 :                 == Note::DisplayFretOption::NoHarmonic && !note->negativeFretUsed()) {
     264              :                 // Check if this string should be ignored in capo params and updated fret
     265           54 :                 nNewFret = fret(note->pitch(), nNewString, pitchOffsetAt(chord->staff(), chord->tick(), nNewString));
     266              :                 // note can be fretted: use string
     267           54 :                 bUsed[nNewString]++;
     268              :             } else {
     269              :                 // no way to fit this note in this tab:
     270              :                 // mark as fretting conflict
     271           13 :                 note->setFretConflict(true);
     272              :                 // store fretting change without affecting chord context
     273           13 :                 if (nFret != nNewFret) {
     274           13 :                     note->undoChangeProperty(Pid::FRET, nNewFret);
     275              :                 }
     276           13 :                 if (nString != nNewString) {
     277           13 :                     note->undoChangeProperty(Pid::STRING, nNewString);
     278              :                 }
     279           13 :                 continue;
     280              :             }
     281              :         }
     282              : 
     283              :         // if the note string (either original or newly assigned) is also used by another note
     284         2019 :         if (note->displayFret() == Note::DisplayFretOption::NoHarmonic && bUsed[nNewString] > 1) {
     285              :             // attempt to find a suitable string, from topmost
     286           44 :             for (int nTempString = 0; nTempString < strings; nTempString++) {
     287           42 :                 if (bUsed[nTempString] < 1
     288           68 :                     && (nTempFret=fret(note->pitch(), nTempString,
     289           68 :                                        pitchOffsetAt(chord->staff(), chord->tick(), nTempString))) != INVALID_FRET_INDEX) {
     290            8 :                     bUsed[nNewString]--;              // free previous string
     291            8 :                     bUsed[nTempString]++;             // and occupy new string
     292            8 :                     nNewFret   = nTempFret;
     293            8 :                     nNewString = nTempString;
     294            8 :                     break;
     295              :                 }
     296              :             }
     297              :         }
     298              : 
     299              :         // Still two notes on one string: try a free string with fret <0 or >maxFrets.
     300         2019 :         if (note->configuration()->negativeFretsAllowed()
     301          103 :             && note->displayFret() == Note::DisplayFretOption::NoHarmonic
     302         2122 :             && bUsed[nNewString] > 1) {
     303            2 :             tryResolveStringConflictWithOutOfRangeFret(note, strings, bUsed, nNewString, nNewFret);
     304              :         }
     305              : 
     306              :         // TODO : try to optimize used fret range, avoiding excessively open positions
     307              : 
     308              :         // if fretting did change, store as a fret change
     309         2019 :         if (nFret != nNewFret) {
     310           54 :             note->undoChangeProperty(Pid::FRET, nNewFret);
     311              :         }
     312         2019 :         if (nString != nNewString) {
     313           55 :             note->undoChangeProperty(Pid::STRING, nNewString);
     314              :         }
     315              :     }
     316              : 
     317              :     // check for any remaining fret conflict
     318         3530 :     for (auto& p : sortedNotes) {
     319         2044 :         Note* note = p.second;
     320         2044 :         if (skipDeadNotes && note->deadNote()) {
     321           12 :             continue;
     322              :         }
     323         2032 :         if (!note->negativeFretUsed() && (note->string() == -1 || bUsed[note->string()] > 1)) {
     324            0 :             note->setFretConflict(true);
     325              :         }
     326              :     }
     327         1486 : }
     328              : 
     329              : //---------------------------------------------------------
     330              : //   frettedStrings
     331              : //    Returns the number of fretted strings.
     332              : //---------------------------------------------------------
     333              : 
     334            0 : int StringData::frettedStrings() const
     335              : {
     336            0 :     int num = 0;
     337            0 :     for (auto s : m_stringTable) {
     338            0 :         if (!s.open) {
     339            0 :             num++;
     340              :         }
     341              :     }
     342            0 :     return num;
     343              : }
     344              : 
     345              : //********************
     346              : // STATIC METHODS
     347              : //********************
     348              : 
     349              : //---------------------------------------------------------
     350              : //   pitchOffsetAt
     351              : //   Computes the pitch offset relevant for string data calculation at the given point.
     352              : //
     353              : //   For string data calculations, pitch offset may depend on transposition, capos and, possibly, ottavas.
     354              : //---------------------------------------------------------
     355              : 
     356         1194 : int StringData::pitchOffsetAt(const Staff* staff)
     357              : {
     358         1194 :     return -(staff ? staff->originPart(Part::MAIN_INSTRUMENT_TICK)->instrument()->transpose().chromatic : 0);
     359              : }
     360              : 
     361         1349 : int StringData::pitchOffsetAt(const Staff* staff, const Fraction& tick)
     362              : {
     363         1349 :     if (!staff) {
     364            0 :         return 0;
     365              :     }
     366         1349 :     int transp = staff->originPart(tick)->instrument(tick)->transpose().chromatic;
     367         1349 :     int offset = -transp + staff->pitchOffset(tick);
     368         1349 :     const CapoParams& capo = staff->capo(tick);
     369         1349 :     if (capo.active) {
     370          228 :         switch (capo.transposeMode) {
     371            2 :         case CapoParams::TransposeMode::PLAYBACK_ONLY:
     372            2 :             break;
     373          226 :         case CapoParams::TransposeMode::STANDARD_ONLY:
     374              :         case CapoParams::TransposeMode::TAB_ONLY:
     375          226 :             offset -= capo.fretPosition;
     376          226 :             break;
     377              :         }
     378              :     }
     379         1349 :     return offset;
     380              : }
     381              : 
     382         2137 : int StringData::pitchOffsetAt(const Staff* staff, const Fraction& tick, int string)
     383              : {
     384         2137 :     if (!staff) {
     385            0 :         return 0;
     386              :     }
     387         2137 :     int transp = staff->originPart(tick)->instrument(tick)->transpose().chromatic;
     388         2137 :     int offset = -transp + staff->pitchOffset(tick);
     389         2137 :     const CapoParams& capo = staff->capo(tick);
     390         2137 :     if (capo.active && muse::contains(capo.ignoredStrings, (string_idx_t)string)) {
     391            4 :         return offset;
     392              :     }
     393         2133 :     if (capo.active) {
     394          139 :         switch (capo.transposeMode) {
     395           34 :         case CapoParams::TransposeMode::PLAYBACK_ONLY:
     396           34 :             break;
     397          105 :         case CapoParams::TransposeMode::STANDARD_ONLY:
     398              :         case CapoParams::TransposeMode::TAB_ONLY:
     399          105 :             offset -= capo.fretPosition;
     400          105 :             break;
     401              :         }
     402              :     }
     403         2133 :     return offset;
     404              : }
     405              : 
     406              : //********************
     407              : // PRIVATE METHODS
     408              : //********************
     409              : 
     410              : //---------------------------------------------------------
     411              : //   convertPitch
     412              : //   Finds string and fret for a note.
     413              : //
     414              : //   Fills *string and *fret with suitable values for pitch / pitchOffset,
     415              : //   using the highest possible string.
     416              : //   If note cannot be fretted, uses fret 0 on nearest string and returns false
     417              : //
     418              : //    Note: Strings are stored internally from lowest (0) to highest (strings()-1),
     419              : //          but the returned *string value references strings in reversed, 'visual', order:
     420              : //          from highest (0) to lowest (strings()-1)
     421              : //---------------------------------------------------------
     422              : 
     423          176 : bool StringData::convertPitch(int pitch, int pitchOffset, int* string, int* fret, const CapoParams& capo) const
     424              : {
     425          176 :     int strings = static_cast<int>(m_stringTable.size());
     426          176 :     if (strings < 1) {
     427           43 :         return false;
     428              :     }
     429              : 
     430          133 :     pitch += pitchOffset;
     431              : 
     432              :     // if above max fret on highest string, fret on first string, but return failure
     433          133 :     if (pitch > m_stringTable.at(strings - 1).pitch + m_frets) {
     434            0 :         *string = 0;
     435            0 :         *fret   = 0;
     436            0 :         return false;
     437              :     }
     438              : 
     439          133 :     if (isFiveStringBanjo()) {
     440              :         // special case: open banjo 5th string
     441            0 :         if (pitch == m_stringTable.at(0).pitch) {
     442            0 :             *string = 4;
     443            0 :             *fret = 0;
     444            0 :             return true;
     445              :         }
     446              :         // test remaining 4 strings from highest to lowest
     447            0 :         for (int i = 4; i > 0; i--) {
     448            0 :             instrString strg = m_stringTable.at(i);
     449            0 :             if (pitch >= strg.pitch) {
     450            0 :                 *string = strings - i - 1;
     451            0 :                 *fret = pitch - strg.pitch;
     452            0 :                 return true;
     453              :             }
     454              :         }
     455              :     } else {
     456              :         // look for a suitable string, starting from the highest
     457              :         // NOTE: this assumes there are always enough frets to fill
     458              :         // the interval between any fretted string and the next
     459          540 :         for (int i = strings - 1; i >= 0; i--) {
     460          519 :             instrString strg = m_stringTable.at(i);
     461          519 :             if (pitch >= strg.pitch) {
     462          112 :                 *string = strings - i - 1;
     463          112 :                 int fretCorrection = (capo.active && muse::contains(capo.ignoredStrings, (string_idx_t)*string)) ? capo.fretPosition : 0;
     464          112 :                 *fret = pitch - strg.pitch + fretCorrection;
     465          112 :                 return true;
     466              :             }
     467              :         }
     468              :     }
     469              : 
     470              :     // if no string found, pitch is below lowest string:
     471              :     // fret on last string, but return failure
     472           21 :     *string = strings - 1;
     473           21 :     *fret   = 0;
     474           21 :     return false;
     475              : }
     476              : 
     477              : //---------------------------------------------------------
     478              : //   getPitch
     479              : //    Returns the pitch corresponding to the string / fret / pitchOffset combination.
     480              : //    Returns INVALID_PITCH if not possible
     481              : //    Note: frets above max fret are accepted.
     482              : //---------------------------------------------------------
     483              : 
     484         4163 : int StringData::getPitch(int string, int fret, int pitchOffset) const
     485              : {
     486         4163 :     size_t strings = m_stringTable.size();
     487         4163 :     if (string < 0 || string >= static_cast<int>(strings)) {
     488            9 :         return INVALID_PITCH;
     489              :     }
     490         4154 :     instrString strg = m_stringTable.at(strings - string - 1);
     491         4154 :     int pitch = strg.pitch - pitchOffset + (strg.open ? 0 : fret);
     492         4154 :     if (strg.startFret > 0 && fret >= strg.startFret) {
     493            0 :         pitch -= strg.startFret; // banjo 5th string adjustment
     494              :     }
     495         4154 :     return pitch;
     496              : }
     497              : 
     498              : //---------------------------------------------------------
     499              : //   fret
     500              : //    Returns the fret corresponding to the pitch / string / pitchOffset combination.
     501              : //    returns INVALID_FRET_INDEX if not possible
     502              : //---------------------------------------------------------
     503              : 
     504          326 : int StringData::fret(int pitch, int string, int pitchOffset) const
     505              : {
     506          326 :     int strings = static_cast<int>(m_stringTable.size());
     507          326 :     if (strings < 1) {                          // no strings at all!
     508            0 :         return INVALID_FRET_INDEX;
     509              :     }
     510              : 
     511          326 :     if (string < 0 || string >= strings) {      // no such a string
     512            2 :         return INVALID_FRET_INDEX;
     513              :     }
     514              : 
     515          324 :     pitch += pitchOffset;
     516              : 
     517          324 :     const instrString& strg = m_stringTable[strings - string - 1];
     518          324 :     int fret = pitch - strg.pitch;
     519          324 :     if (fret > 0 && strg.startFret > 0) {
     520            0 :         fret += strg.startFret;  // banjo 5th string adjustment
     521              :     }
     522              : 
     523              :     // fret number is invalid or string cannot be fretted
     524          324 :     if (fret < 0 || fret > m_frets || (fret > 0 && strg.open)) {
     525           34 :         return INVALID_FRET_INDEX;
     526              :     }
     527          290 :     return fret;
     528              : }
     529              : 
     530              : //---------------------------------------------------------
     531              : //   tryResolveStringConflictWithOutOfRangeFret
     532              : //    After in-range conflict resolution fails: assign an unused string using raw
     533              : //    fret (may be <0 or >maxFrets). Chooses smallest distance to [0, maxFrets];
     534              : //    ties break toward lower string index.
     535              : //---------------------------------------------------------
     536              : 
     537            2 : bool StringData::tryResolveStringConflictWithOutOfRangeFret(const Note* note, int numStrings, std::vector<int>& bUsed,
     538              :                                                             int& nNewString, int& nNewFret) const
     539              : {
     540            2 :     int bestString = -1;
     541            2 :     int bestDistance = INT32_MAX;
     542            2 :     int bestRawFret = 0;
     543              : 
     544           14 :     for (int s = 0; s < numStrings; s++) {
     545           12 :         if (bUsed[static_cast<size_t>(s)] >= 1) {
     546            3 :             continue;
     547              :         }
     548            9 :         const int openPitch = m_stringTable.at(m_stringTable.size() - s - 1).pitch;
     549            9 :         const int rawFret = note->pitch() + pitchOffsetAt(note->staff(), note->tick(), s) - openPitch;   // not clamped to 0..m_frets
     550              :         int distance;
     551            9 :         if (rawFret < 0) {
     552            9 :             distance = -rawFret;
     553            0 :         } else if (rawFret > m_frets) {
     554            0 :             distance = rawFret - m_frets;
     555              :         } else {
     556            0 :             continue;   // only out-of-range frets here; in-range tried above in fretChords
     557              :         }
     558              : 
     559            9 :         if (distance < bestDistance
     560            0 :             || (distance == bestDistance && (bestString < 0 || s < bestString))) {
     561            9 :             bestDistance = distance;
     562            9 :             bestRawFret = rawFret;
     563            9 :             bestString = s;
     564              :         }
     565              :     }
     566              : 
     567            2 :     if (bestString < 0) {
     568            0 :         return false;
     569              :     }
     570              : 
     571            2 :     bUsed[static_cast<size_t>(nNewString)]--;
     572            2 :     bUsed[static_cast<size_t>(bestString)]++;
     573            2 :     nNewFret = bestRawFret;
     574            2 :     nNewString = bestString;
     575            2 :     return true;
     576              : }
     577              : 
     578          257 : static bool skipTabNote(const Note* n, bool skipDeadNotes)
     579              : {
     580          257 :     return n->displayFret() != Note::DisplayFretOption::NoHarmonic
     581          257 :            || (skipDeadNotes && n->deadNote());
     582              : }
     583              : 
     584              : //---------------------------------------------------------
     585              : //   hasPendingPitchChange
     586              : //    True if any note's pitch doesn't match its current string/fret.
     587              : //---------------------------------------------------------
     588              : 
     589           50 : bool StringData::hasPendingPitchChange(const Chord* chord) const
     590              : {
     591           50 :     const bool skipDead = chord->configuration()->keepDeadNotesUnchangedOnTranspose();
     592          116 :     for (Note* note : chord->notes()) {
     593           84 :         if (skipTabNote(note, skipDead)) {
     594            2 :             continue;
     595              :         }
     596           82 :         if (note->string() < 0) {
     597            0 :             continue;
     598              :         }
     599           82 :         if (getPitch(note->string(), note->fret(), chord->staff(), chord->tick()) != note->pitch()) {
     600           18 :             return true;
     601              :         }
     602              :     }
     603           32 :     return false;
     604              : }
     605              : 
     606              : //---------------------------------------------------------
     607              : //   updateFretsOnSameStrings
     608              : //    Keep each note on its string, adjust fret to match the new pitch.
     609              : //    Set INVALID if the note can't stay on its string.
     610              : //---------------------------------------------------------
     611              : 
     612           18 : void StringData::updateFretsOnSameStrings(const Chord* chord) const
     613              : {
     614           18 :     const bool skipDead = chord->configuration()->keepDeadNotesUnchangedOnTranspose();
     615              : 
     616           62 :     for (Note* note : chord->notes()) {
     617           44 :         if (skipTabNote(note, skipDead)) {
     618            4 :             continue;
     619              :         }
     620           40 :         if (note->string() < 0) {
     621            0 :             note->setString(INVALID_STRING_INDEX);
     622            0 :             note->setFret(INVALID_FRET_INDEX);
     623            0 :             continue;
     624              :         }
     625           40 :         int pitch = getPitch(note->string(), note->fret(), chord->staff(), chord->tick());
     626           40 :         int newFret = note->fret() + note->pitch() - pitch;
     627           40 :         if (newFret < 0 && !note->configuration()->negativeFretsAllowed()) {
     628            0 :             note->setString(INVALID_STRING_INDEX);
     629            0 :             note->setFret(INVALID_FRET_INDEX);
     630            0 :             continue;
     631              :         }
     632           40 :         if (newFret < 0) {
     633              :             // Don't create a negative fret on a string that already holds another note — this would cause a false conflict
     634           17 :             bool sameStringConflict = false;
     635           54 :             for (Note* other : chord->notes()) {
     636           38 :                 if (other != note && !skipTabNote(other, skipDead) && other->string() == note->string()) {
     637            1 :                     sameStringConflict = true;
     638            1 :                     break;
     639              :                 }
     640              :             }
     641           17 :             if (sameStringConflict) {
     642            1 :                 note->setString(INVALID_STRING_INDEX);
     643            1 :                 note->setFret(INVALID_FRET_INDEX);
     644            1 :                 continue;
     645              :             }
     646              :         }
     647           39 :         note->setFret(newFret);
     648              :     }
     649           18 : }
     650              : 
     651              : //---------------------------------------------------------
     652              : //   preferBassStringForNegativeFret
     653              : //    If a non-bass note landed on a deep negative fret and the bass
     654              : //    string is free, move it there when the bass fret is shallower.
     655              : //---------------------------------------------------------
     656              : 
     657           18 : void StringData::preferBassStringForNegativeFret(const Chord* chord) const
     658              : {
     659           18 :     const bool skipDead = chord->configuration()->keepDeadNotesUnchangedOnTranspose();
     660           18 :     const int bassStr = static_cast<int>(strings()) - 1;
     661           18 :     if (bassStr <= 0) {
     662            0 :         return;
     663              :     }
     664           18 :     const int openLow = m_stringTable.at(0).pitch;
     665              : 
     666            9 :     auto bassTaken = [&](const Note* except) {
     667           16 :         for (Note* o : chord->notes()) {
     668           12 :             if (o != except && !skipTabNote(o, skipDead) && o->string() == bassStr) {
     669            5 :                 return true;
     670              :             }
     671              :         }
     672            4 :         return false;
     673           18 :     };
     674              : 
     675           62 :     for (Note* note : chord->notes()) {
     676           44 :         if (skipTabNote(note, skipDead) || note->string() < 0 || note->string() == bassStr || note->fret() >= 0) {
     677           35 :             continue;
     678              :         }
     679            9 :         if (bassTaken(note)) {
     680            5 :             continue;
     681              :         }
     682            4 :         const int rawLow = note->pitch() + pitchOffsetAt(chord->staff(), chord->tick(), bassStr) - openLow;
     683            4 :         if (rawLow < 0 && rawLow > note->fret()) {
     684            1 :             note->setString(bassStr);
     685            1 :             note->setFret(rawLow);
     686              :         }
     687              :     }
     688              : }
     689              : 
     690              : //---------------------------------------------------------
     691              : //   reassignNegativeFretNotes
     692              : //    Move negative-fret notes to a non-negative string if available.
     693              : //    Reset displaced chord-mates for fretChords to reassign.
     694              : //---------------------------------------------------------
     695              : 
     696           18 : void StringData::reassignNegativeFretNotes(const Chord* chord) const
     697              : {
     698           18 :     const bool skipDead = chord->configuration()->keepDeadNotesUnchangedOnTranspose();
     699           18 :     const int pitchOffsetWithCapo = pitchOffsetAt(chord->staff(), chord->tick());
     700           18 :     const CapoParams& capo = chord->staff()->capo(chord->tick());
     701              : 
     702           62 :     for (Note* note : chord->notes()) {
     703           44 :         if (skipTabNote(note, skipDead) || note->string() < 0 || note->fret() >= 0) {
     704           36 :             continue;
     705              :         }
     706           16 :         int targetStr = -1;
     707           16 :         int targetFret = -1;
     708           16 :         if (!convertPitch(note->pitch(), pitchOffsetWithCapo, &targetStr, &targetFret, capo)) {
     709            8 :             continue;
     710              :         }
     711            8 :         Note* positiveOnTarget = nullptr;
     712           26 :         for (Note* o : chord->notes()) {
     713           19 :             if (o != note && !skipTabNote(o, skipDead) && o->string() == targetStr && o->fret() >= 0) {
     714            1 :                 positiveOnTarget = o;
     715            1 :                 break;
     716              :             }
     717              :         }
     718            8 :         if (positiveOnTarget) {
     719            1 :             note->setString(targetStr);
     720            1 :             note->setFret(targetFret);
     721            1 :             positiveOnTarget->setString(INVALID_STRING_INDEX);
     722            1 :             positiveOnTarget->setFret(INVALID_FRET_INDEX);
     723              :         } else {
     724            7 :             note->setString(INVALID_STRING_INDEX);
     725            7 :             note->setFret(INVALID_FRET_INDEX);
     726              :         }
     727              :     }
     728           18 : }
     729              : 
     730              : //---------------------------------------------------------
     731              : //   sortChordNotesUseSameString
     732              : //    Tries to keep each note on its currently assigned string when the
     733              : //    chord's pitches are transposed, updating only the fret.
     734              : //---------------------------------------------------------
     735              : 
     736           50 : void StringData::sortChordNotesUseSameString(const Chord* chord) const
     737              : {
     738           50 :     if (!hasPendingPitchChange(chord)) {
     739           32 :         return;
     740              :     }
     741              : 
     742           18 :     updateFretsOnSameStrings(chord);
     743              : 
     744           18 :     if (!chord->configuration()->negativeFretsAllowed()) {
     745            0 :         return;
     746              :     }
     747              : 
     748           18 :     preferBassStringForNegativeFret(chord);
     749           18 :     reassignNegativeFretNotes(chord);
     750              : }
     751              : 
     752              : //---------------------------------------------------------
     753              : //   sortChordNotes
     754              : //    Adds to sortedNotes the notes of Chord in string/pitch order
     755              : //    Note: notes are sorted first by string (top string being 0),
     756              : //          then by negated pitch (higher pitches resulting in lower key),
     757              : //          then by order of submission to disambiguate notes with the same pitch.
     758              : //          Everything else being equal, this makes notes in higher-numbered voices
     759              : //          to be sorted after notes in lower-numbered voices (voice 2 after voice 1 and so on)
     760              : //    Notes without a string assigned yet, are sorted according to the lowest string which can accommodate them.
     761              : //---------------------------------------------------------
     762              : 
     763         1686 : void StringData::sortChordNotes(std::map<int, Note*>& sortedNotes, const Chord* chord, int* count) const
     764              : {
     765         1686 :     bool useSameString = chord->configuration()->preferSameStringForTranspose();
     766         1686 :     const bool skipDeadNotes = chord->configuration()->keepDeadNotesUnchangedOnTranspose();
     767         1686 :     int transp = chord->staff() ? chord->originPart()->instrument(chord->tick())->transpose().chromatic : 0;
     768         1686 :     int pitchOffset = -transp + chord->staff()->pitchOffset(chord->segment()->tick());
     769              : 
     770         1686 :     if (useSameString) {
     771           50 :         sortChordNotesUseSameString(chord);
     772              :     }
     773              : 
     774         3730 :     for (Note* note : chord->notes()) {
     775         2044 :         if (note->displayFret() != Note::DisplayFretOption::NoHarmonic) {
     776            0 :             continue;
     777              :         }
     778              : 
     779         2044 :         int string = note->string();
     780         2044 :         int noteFret = note->fret();
     781              : 
     782         2044 :         int pitch = getPitch(string, noteFret, pitchOffsetAt(chord->staff(), chord->tick(), string));
     783              :         // if note not fretted yet or current fretting no longer valid,
     784              :         // use most convenient string as key
     785         2044 :         if (!(skipDeadNotes && note->deadNote()) && !note->negativeFretUsed()
     786         6093 :             && (string <= INVALID_STRING_INDEX || noteFret <= INVALID_FRET_INDEX
     787         2005 :                 || (pitchIsValid(pitch) && pitch != note->pitch()))) {
     788           67 :             note->setString(INVALID_STRING_INDEX);
     789           67 :             note->setFret(INVALID_FRET_INDEX);
     790              :         }
     791              : 
     792         2044 :         int key = string * 100000;
     793         2044 :         if (string == INVALID_STRING_INDEX && note->configuration()->negativeFretsAllowed()) {
     794              :             // No string assigned yet: lower note first, then higher (transpose in one go or in steps must match).
     795            9 :             key += (note->pitch() + pitchOffset) * 100 + *count;
     796              :         } else {
     797         2035 :             key += -(note->pitch() + pitchOffset) * 100 + *count;   // disambiguate notes of equal pitch
     798              :         }
     799         2044 :         sortedNotes.insert({ key, note });
     800         2044 :         (*count)++;
     801              :     }
     802         1686 : }
     803              : 
     804              : //---------------------------------------------------------
     805              : //   configBanjo5thString
     806              : //   Assumes isFiveStringBanjo() has already been called.
     807              : //   This method looks at the banjo tuning and sets startFret
     808              : //   appropriately.
     809              : //---------------------------------------------------------
     810              : 
     811           18 : void StringData::configBanjo5thString()
     812              : {
     813              :     // banjo 5th string (pitch 67 == G)
     814           18 :     instrString& strg5 = m_stringTable[0];
     815              : 
     816           18 :     m_frets = 24; // not needed after bug #316931 is fixed
     817              : 
     818              :     // adjust startFret if using a 5th string capo (6..12)
     819           18 :     if (strg5.pitch > 67 && strg5.pitch < 74) {
     820            0 :         strg5.startFret = strg5.pitch - 62;
     821              :     } else {
     822           18 :         strg5.startFret = 5;  // no 5th string capo
     823              :     }
     824           18 : }
     825              : 
     826              : //---------------------------------------------------------
     827              : //   adjustBanjo5thFret
     828              : //   Convert 5th string fret number from (0, 1, 2, 3...) to (0, 6, 7, 8...).
     829              : //   Called from import (GuitarPro mostly)
     830              : //   Returns adjusted fret number
     831              : //---------------------------------------------------------
     832              : 
     833            0 : int StringData::adjustBanjo5thFret(int fret) const
     834              : {
     835            0 :     return (fret > 0 && isFiveStringBanjo()) ? fret + m_stringTable[0].startFret : fret;
     836              : }
     837              : 
     838              : //---------------------------------------------------------
     839              : //    isFiveStringBanjo
     840              : //    Based only on number of strings and tuning - other info
     841              : //    may not be available when this is called. Checks 5th string
     842              : //    pitch is higher than 4th (i.e. not a 5 string bass)
     843              : //---------------------------------------------------------
     844              : 
     845         2897 : bool StringData::isFiveStringBanjo() const
     846              : {
     847         2897 :     return m_stringTable.size() == 5 && m_stringTable[0].pitch > m_stringTable[1].pitch;
     848              : }
     849              : }
        

Generated by: LCOV version 2.0-1