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) 2026 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 "editenharmonicspelling.h"
24 :
25 : #include <algorithm>
26 : #include <vector>
27 :
28 : #include "transpose.h"
29 :
30 : #include "../dom/chord.h"
31 : #include "../dom/key.h"
32 : #include "../dom/note.h"
33 : #include "../dom/part.h"
34 : #include "../dom/pitchspelling.h"
35 : #include "../dom/score.h"
36 : #include "../dom/segment.h"
37 : #include "../dom/select.h"
38 : #include "../dom/staff.h"
39 :
40 : using namespace mu::engraving;
41 :
42 : // table of alternative spellings for one octave
43 : // each entry is the TPC of the note
44 : // tab1 does not contain double sharps
45 : // tab2 does not contain double flats
46 :
47 : static constexpr int tab1[24] = {
48 : 14, 2, // 60 C Dbb
49 : 21, 9, // 61 C# Db
50 : 16, 4, // 62 D Ebb
51 : 23, 11, // 63 D# Eb
52 : 18, 6, // 64 E Fb
53 : 13, 1, // 65 F Gbb
54 : 20, 8, // 66 F# Gb
55 : 15, 3, // 67 G Abb
56 : 22, 10, // 68 G# Ab
57 : 17, 5, // 69 A Bbb
58 : 24, 12, // 70 A# Bb
59 : 19, 7, // 71 B Cb
60 : };
61 :
62 : static constexpr int tab2[24] = {
63 : 26, 14, // 60 B# C
64 : 21, 9, // 61 C# Db
65 : 28, 16, // 62 C## D
66 : 23, 11, // 63 D# Eb
67 : 30, 18, // 64 D## E
68 : 25, 13, // 65 E# F
69 : 20, 8, // 66 F# Gb
70 : 27, 15, // 67 F## G
71 : 22, 10, // 68 G# Ab
72 : 29, 17, // 69 G## A
73 : 24, 12, // 70 A# Bb
74 : 31, 19, // 71 A## B
75 : };
76 :
77 : static constexpr int intervalPenalty[13] = {
78 : 0, 0, 0, 0, 0, 0, 1, 3, 1, 1, 1, 3, 3
79 : };
80 :
81 : static constexpr int enharmonicSpelling[15][34] = {
82 : {
83 : //Ces f c g d a e b
84 : 1, 1, 1, 1, 1, 1, // bb
85 : 0, 0, 0, 0, 0, 0, 0, // b
86 : 1, 1, 1, 1, 1, 1, 1,
87 : 1, 1, 1, 1, 1, 1, 1, // #
88 : 1, 1, 1, 1, 1, 1, 1 // ##
89 : },
90 : {
91 : //Ges f c g d a e b
92 : 1, 1, 1, 1, 1, 1, // bb
93 : 1, 0, 0, 0, 0, 0, 0, // b
94 : 0, 1, 1, 1, 1, 1, 1,
95 : 1, 1, 1, 1, 1, 1, 1, // #
96 : 1, 1, 1, 1, 1, 1, 1 // ##
97 : },
98 : {
99 : //Des f c g d a e b
100 : 1, 1, 1, 1, 1, 1, // bb
101 : 1, 1, 0, 0, 0, 0, 0, // b
102 : 0, 0, 1, 1, 1, 1, 1,
103 : 1, 1, 1, 1, 1, 1, 1, // #
104 : 1, 1, 1, 1, 1, 1, 1 // ##
105 : },
106 : {
107 : //As f c g d a e b
108 : 1, 1, 1, 1, 1, 1, // bb
109 : 1, 1, 0, 0, 0, 0, 0, // b
110 : 0, 0, 0, 1, 1, 1, 1,
111 : 0, 1, 1, 1, 1, 1, 1, // #
112 : 1, 1, 1, 1, 1, 1, 1 // ##
113 : },
114 : {
115 : //Es f c g d a e b
116 : 1, 1, 1, 1, 1, 1, // bb
117 : 1, 1, 0, 0, 0, 0, 0, // b
118 : 0, 0, 0, 0, 1, 1, 1,
119 : 0, 0, 1, 1, 1, 1, 1, // #
120 : 1, 1, 1, 1, 1, 1, 1 // ##
121 : },
122 : {
123 : //Bb f c g d a e b
124 : 1, 1, 1, 1, 1, 1, // bb
125 : 1, 1, 0, 0, 0, 0, 0, // b
126 : 0, 0, 0, 0, 0, 1, 1,
127 : 1, 0, 0, 1, 1, 1, 1, // # // (ws) penalty for f#
128 : 1, 1, 1, 1, 1, 1, 1 // ##
129 : },
130 : {
131 : //F f c g d a e b // extra penalty for a# b
132 : 1, 1, 1, 1, 1, 1, // bb
133 : 1, 1, 0, 0, 0, 0, 0, // b
134 : 0, 0, 0, 0, 0, 0, 1,
135 : 0, 0, 0, 0, 1, 1, 1, // #
136 : 1, 1, 1, 1, 1, 1, 1 // ##
137 : },
138 : {
139 : //C f c g d a e b
140 : 1, 1, 1, 1, 1, 1, // bb
141 : 1, 1, 0, 0, 0, 0, 0, // b
142 : 0, 0, 0, 0, 0, 0, 0,
143 : 0, 0, 0, 0, 0, 1, 1, // #
144 : 1, 1, 1, 1, 1, 1, 1 // ##
145 : },
146 : {
147 : //G f c g d a e b
148 : 1, 1, 1, 1, 1, 1, // bb
149 : 1, 1, 1, 0, 0, 0, 0, // b
150 : 1, 0, 0, 0, 0, 0, 0,
151 : 0, 0, 0, 0, 0, 1, 1, // #
152 : 1, 1, 1, 1, 1, 1, 1 // ##
153 : },
154 : {
155 : //D f c g d a e b
156 : 1, 1, 1, 1, 1, 1, // bb
157 : 1, 1, 1, 1, 0, 0, 0, // b
158 : 1, 1, 0, 0, 0, 0, 0,
159 : 0, 0, 0, 0, 0, 1, 1, // #
160 : 1, 1, 1, 1, 1, 1, 1 // ##
161 : },
162 : {
163 : //A f c g d a e b
164 : 1, 1, 1, 1, 1, 1, // bb
165 : 1, 1, 1, 1, 1, 0, 0, // b
166 : 1, 1, 1, 0, 0, 0, 0,
167 : 0, 0, 0, 0, 0, 1, 1, // #
168 : 1, 1, 1, 1, 1, 1, 1 // ##
169 : },
170 : {
171 : //E f c g d a e b
172 : 1, 1, 1, 1, 1, 1, // bb
173 : 1, 1, 1, 1, 1, 1, 0, // b
174 : 1, 1, 1, 1, 0, 0, 0,
175 : 0, 0, 0, 0, 0, 1, 1, // #
176 : 0, 0, 1, 1, 1, 1, 1 // ##
177 : },
178 : {
179 : //H f c g d a e b
180 : 1, 1, 1, 1, 1, 1, // bb
181 : 1, 1, 1, 1, 1, 1, 1, // b
182 : 1, 1, 1, 1, 1, 0, 0,
183 : 0, 0, 0, 0, 0, 1, 1, // #
184 : 1, 1, 1, 1, 1, 1, 1 // ##
185 : },
186 : {
187 : //Fis f c g d a e b
188 : 1, 1, 1, 1, 1, 1, // bb
189 : 1, 1, 1, 1, 1, 1, 1, // b
190 : 100, 1, 1, 1, 1, 1, 0,
191 : 0, 0, 0, 0, 0, 0, 0, // #
192 : 0, 1, 1, 1, 1, 1, 1 // ##
193 : },
194 : {
195 : //Cis f c g d a e b
196 : 1, 1, 1, 1, 1, 1, // bb
197 : 1, 1, 0, 0, 0, 0, 0, // b //Fis
198 : 100, 1, 1, 1, 1, 0, 0,
199 : 0, 0, 0, 0, 0, 0, 0, // #
200 : 0, 0, 1, 1, 1, 1, 1 // ##
201 : }
202 : };
203 :
204 0 : static constexpr int penalty(int lof1, int lof2, int k)
205 : {
206 0 : IF_ASSERT_FAILED((k >= 0 && k < 15)) {
207 0 : return 0;
208 : }
209 0 : assert(lof1 >= 0 && lof1 < 34);
210 0 : assert(lof2 >= 0 && lof2 < 34);
211 0 : int penalty = enharmonicSpelling[k][lof1] * 4 + enharmonicSpelling[k][lof2] * 4;
212 0 : int distance = lof2 > lof1 ? lof2 - lof1 : lof1 - lof2;
213 0 : if (distance > 12) {
214 0 : penalty += 3;
215 : } else {
216 0 : penalty += intervalPenalty[distance];
217 : }
218 0 : return penalty;
219 : }
220 :
221 0 : static int computeWindow(const std::vector<Note*>& notes, int start, int end)
222 : {
223 0 : int p = 10000;
224 0 : int idx = -1;
225 : int pitch[10];
226 : int key[10];
227 :
228 0 : int i = start;
229 0 : int k = 0;
230 0 : while (i < end) {
231 0 : pitch[k] = notes[i]->pitch() % 12;
232 0 : Fraction tick = notes[i]->chord()->tick();
233 0 : key[k] = int(notes[i]->staff()->key(tick)) + 7;
234 0 : if (key[k] < 0 || key[k] > 14) {
235 0 : LOGD("illegal key at tick %d: %d, window %d-%d",
236 0 : tick.ticks(), key[k] - 7, start, end);
237 0 : return 0;
238 : // abort();
239 : }
240 0 : ++k;
241 0 : ++i;
242 : }
243 :
244 0 : for (; k < 10; ++k) {
245 0 : pitch[k] = pitch[k - 1];
246 0 : key[k] = key[k - 1];
247 : }
248 :
249 0 : for (i = 0; i < 512; ++i) {
250 0 : int pa = 0;
251 0 : int pb = 0;
252 0 : int l = pitch[0] * 2 + (i & 1);
253 0 : assert(l >= 0 && l <= static_cast<int>(sizeof(tab1) / sizeof(*tab1)));
254 0 : int lof1a = tab1[l];
255 0 : int lof1b = tab2[l];
256 :
257 0 : for (k = 1; k < 10; ++k) {
258 0 : int l1 = pitch[k] * 2 + ((i & (1 << k)) >> k);
259 0 : assert(l1 >= 0 && l1 <= static_cast<int>(sizeof(tab1) / sizeof(*tab1)));
260 0 : int lof2a = tab1[l1];
261 0 : int lof2b = tab2[l1];
262 0 : pa += penalty(lof1a, lof2a, key[k]);
263 0 : pb += penalty(lof1b, lof2b, key[k]);
264 0 : lof1a = lof2a;
265 0 : lof1b = lof2b;
266 : }
267 0 : if (pa < pb) {
268 0 : if (pa < p) {
269 0 : p = pa;
270 0 : idx = i;
271 : }
272 : } else {
273 0 : if (pb < p) {
274 0 : p = pb;
275 0 : idx = i * -1;
276 : }
277 : }
278 : }
279 :
280 0 : return idx;
281 : }
282 :
283 0 : static void changeAllTpcs(Note* n, int tpc1)
284 : {
285 0 : if (!n) {
286 0 : return;
287 : }
288 0 : Interval v;
289 0 : Fraction tick = n->chord() ? n->chord()->tick() : Fraction(-1, 1);
290 0 : if (n->part() && n->part()->instrument(tick)) {
291 0 : v = n->staff()->transpose(tick);
292 0 : v.flip();
293 : }
294 0 : int tpc2 = Transpose::transposeTpc(tpc1, v, true);
295 0 : n->undoChangeProperty(Pid::TPC1, tpc1);
296 0 : n->undoChangeProperty(Pid::TPC2, tpc2);
297 0 : for (Note* tied : n->tiedNotes()) {
298 0 : tied->undoChangeProperty(Pid::TPC1, tpc1);
299 0 : tied->undoChangeProperty(Pid::TPC2, tpc2);
300 0 : }
301 : }
302 :
303 4 : static void spellNotelist(std::vector<Note*>& notes)
304 : {
305 : static constexpr int WINDOW = 9;
306 :
307 4 : int n = int(notes.size());
308 :
309 4 : int start = 0;
310 4 : while (start < n) {
311 0 : int end = start + WINDOW;
312 0 : if (end > n) {
313 0 : end = n;
314 : }
315 0 : int opt = computeWindow(notes, start, end);
316 : const int* tab;
317 0 : if (opt < 0) {
318 0 : tab = tab2;
319 0 : opt *= -1;
320 : } else {
321 0 : tab = tab1;
322 : }
323 :
324 0 : if (start == 0) {
325 0 : changeAllTpcs(notes[0], tab[(notes[0]->pitch() % 12) * 2 + (opt & 1)]);
326 0 : if (n > 1) {
327 0 : changeAllTpcs(notes[1], tab[(notes[1]->pitch() % 12) * 2 + ((opt & 2) >> 1)]);
328 : }
329 0 : if (n > 2) {
330 0 : changeAllTpcs(notes[2], tab[(notes[2]->pitch() % 12) * 2 + ((opt & 4) >> 2)]);
331 : }
332 : }
333 0 : if ((end - start) >= 6) {
334 0 : changeAllTpcs(notes[start + 3], tab[(notes[start + 3]->pitch() % 12) * 2 + ((opt & 8) >> 3)]);
335 0 : changeAllTpcs(notes[start + 4], tab[(notes[start + 4]->pitch() % 12) * 2 + ((opt & 16) >> 4)]);
336 0 : changeAllTpcs(notes[start + 5], tab[(notes[start + 5]->pitch() % 12) * 2 + ((opt & 32) >> 5)]);
337 : }
338 0 : if (end == n) {
339 0 : int n1 = end - start;
340 : int k;
341 0 : switch (n1 - 6) {
342 0 : case 3:
343 0 : k = end - start - 3;
344 0 : changeAllTpcs(notes[end - 3], tab[(notes[end - 3]->pitch() % 12) * 2 + ((opt & (1 << k)) >> k)]);
345 : [[fallthrough]];
346 0 : case 2:
347 0 : k = end - start - 2;
348 0 : changeAllTpcs(notes[end - 2], tab[(notes[end - 2]->pitch() % 12) * 2 + ((opt & (1 << k)) >> k)]);
349 : [[fallthrough]];
350 0 : case 1:
351 0 : k = end - start - 1;
352 0 : changeAllTpcs(notes[end - 1], tab[(notes[end - 1]->pitch() % 12) * 2 + ((opt & (1 << k)) >> k)]);
353 : }
354 0 : break;
355 : }
356 : // advance to next window
357 0 : start += 3;
358 : }
359 4 : }
360 :
361 4 : void EditEnharmonicSpelling::spell(Score* score)
362 : {
363 4 : IF_ASSERT_FAILED(score) {
364 0 : return;
365 : }
366 :
367 8 : for (staff_idx_t i = 0; i < score->nstaves(); ++i) {
368 4 : std::vector<Note*> notes;
369 196 : for (Segment* s = score->firstSegment(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
370 192 : track_idx_t strack = i * VOICES;
371 192 : track_idx_t etrack = strack + VOICES;
372 960 : for (track_idx_t track = strack; track < etrack; ++track) {
373 768 : EngravingItem* e = s->element(track);
374 768 : if (!e || !e->isChord()) {
375 768 : continue;
376 : }
377 :
378 0 : Chord* chord = toChord(e);
379 0 : std::copy_if(chord->notes().begin(), chord->notes().end(), std::back_inserter(notes), [score](EngravingItem* chordElement) {
380 0 : return score->selection().isNone() || chordElement->selected();
381 : });
382 :
383 0 : for (Chord* graceChord : chord->graceNotes()) {
384 0 : std::copy_if(graceChord->notes().begin(), graceChord->notes().end(), std::back_inserter(notes),
385 0 : [score](EngravingItem* chordElement) {
386 0 : return score->selection().isNone() || chordElement->selected();
387 : });
388 : }
389 : }
390 : }
391 :
392 4 : spellNotelist(notes);
393 4 : }
394 : }
395 :
396 0 : void EditEnharmonicSpelling::spellWithSharpsOrFlats(Score* score, Prefer prefer)
397 : {
398 0 : IF_ASSERT_FAILED(score) {
399 0 : return;
400 : }
401 :
402 0 : std::vector<Note*> notes = score->selection().noteList();
403 0 : for (Note* note : notes) {
404 0 : Interval interval = note->part()->instrument(note->chord()->tick())->transpose();
405 0 : int tpc1 = TPC_INVALID;
406 0 : int tpc2 = TPC_INVALID;
407 0 : if (note->staff()->concertPitch() || interval.isZero()) {
408 : // Note: using Key::C always and ignoring actual key signature. Otherwise, "respell with flats" would still use sharps
409 : // sometimes if they're in the key, and vice versa, which seems contrary to the intent of the command.
410 0 : tpc1 = pitch2tpc(note->pitch(), Key::C, prefer);
411 0 : interval.flip();
412 0 : tpc2 = Transpose::transposeTpc(tpc1, interval, true);
413 : } else {
414 : // Spell the transposed pitch first, then convert to concert pitch
415 0 : int writtenPitch = note->pitch() - interval.chromatic;
416 0 : tpc2 = pitch2tpc(writtenPitch, Key::C, prefer);
417 0 : tpc1 = Transpose::transposeTpc(tpc2, interval, true);
418 : }
419 :
420 0 : note->undoChangeProperty(Pid::TPC1, tpc1);
421 0 : note->undoChangeProperty(Pid::TPC2, tpc2);
422 0 : for (Note* tiedNote : note->tiedNotes()) {
423 0 : tiedNote->undoChangeProperty(Pid::TPC1, tpc1);
424 0 : tiedNote->undoChangeProperty(Pid::TPC2, tpc2);
425 0 : }
426 : }
427 0 : }
428 :
429 10 : void EditEnharmonicSpelling::changeEnharmonicSpelling(Score* score, bool both)
430 : {
431 10 : IF_ASSERT_FAILED(score) {
432 0 : return;
433 : }
434 :
435 : static constexpr int tpcTable[36] = {
436 : 26, 14, 2, // 60 B# C Dbb
437 : 21, 21, 9, // 61 C# C# Db
438 : 28, 16, 4, // 62 C## D Ebb
439 : 23, 23, 11, // 63 D# D# Eb
440 : 30, 18, 6, // 64 D## E Fb
441 : 25, 13, 1, // 65 E# F Gbb
442 : 20, 20, 8, // 66 F# F# Gb
443 : 27, 15, 3, // 67 F## G Abb
444 : 22, 22, 10, // 68 G# G# Ab
445 : 29, 17, 5, // 69 G## A Bbb
446 : 24, 24, 12, // 70 A# A# Bb
447 : 31, 19, 7, // 71 A## B Cb
448 : };
449 :
450 10 : std::vector<Note*> notes = score->selection().uniqueNotes();
451 140 : for (Note* note : notes) {
452 130 : Staff* staff = note->staff();
453 130 : if (note->originPart()->instrument(note->tick())->useDrumset()) {
454 0 : continue;
455 : }
456 :
457 130 : if (staff->isTabStaff(note->tick())) {
458 0 : int string = note->line() + (both ? 1 : -1);
459 0 : int fret = note->originPart()->stringData(note->tick(), staff->idx())->fret(note->pitch(), string, staff);
460 0 : if (fret != -1) {
461 0 : note->undoChangeProperty(Pid::FRET, fret);
462 0 : note->undoChangeProperty(Pid::STRING, string);
463 : }
464 0 : continue;
465 0 : }
466 :
467 130 : int tpc = note->tpc();
468 2246 : for (int i = 0; i < 36; ++i) {
469 2246 : if (tpcTable[i] != tpc) {
470 2116 : continue;
471 : }
472 :
473 130 : if ((i % 3) < 2) {
474 78 : tpc = tpcTable[i] == tpcTable[i + 1] ? tpcTable[i + 2] : tpcTable[i + 1];
475 : } else {
476 52 : tpc = tpcTable[i - 2];
477 : }
478 130 : break;
479 : }
480 :
481 130 : note->undoSetTpc(tpc);
482 130 : if (both || staff->part()->instrument(note->chord()->tick())->transpose().isZero()) {
483 65 : int transposedTpc = note->transposeTpc(tpc);
484 65 : if (note->concertPitch()) {
485 0 : note->undoChangeProperty(Pid::TPC2, transposedTpc);
486 : } else {
487 65 : note->undoChangeProperty(Pid::TPC1, transposedTpc);
488 : }
489 : }
490 : }
491 10 : }
492 :
493 16 : int EditEnharmonicSpelling::bestEnharmonicFit(const std::vector<int> tpcs, Key key)
494 : {
495 16 : int keyIndex = int(key) - int(Key::MIN);
496 16 : if (keyIndex < 0 || keyIndex >= int(Key::NUM_OF)) {
497 0 : return tpcs.front();
498 : }
499 :
500 : // Highest penalty in enharmonicSpelling available (100) + 1
501 16 : int bestPenalty = 101;
502 16 : int closestTpc = Tpc::TPC_INVALID;
503 :
504 38 : for (int tpc : tpcs) {
505 22 : if (tpc == Tpc::TPC_INVALID) {
506 0 : continue;
507 : }
508 :
509 22 : int lof = tpc - Tpc::TPC_MIN;
510 22 : if (lof < 0 || lof >= 34) {
511 0 : continue;
512 : }
513 :
514 22 : int penalty = enharmonicSpelling[keyIndex][lof];
515 :
516 22 : if (penalty < bestPenalty) {
517 20 : bestPenalty = penalty;
518 20 : closestTpc = tpc;
519 : }
520 : }
521 :
522 16 : return closestTpc;
523 : }
|