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 : }
|