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
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 "editslashnotation.h"
24 :
25 : #include "noteinput.h"
26 :
27 : #include "containers.h"
28 :
29 : #include "../dom/chord.h"
30 : #include "../dom/durationtype.h"
31 : #include "../dom/input.h"
32 : #include "../dom/linkedobjects.h"
33 : #include "../dom/measure.h"
34 : #include "../dom/note.h"
35 : #include "../dom/rest.h"
36 : #include "../dom/score.h"
37 : #include "../dom/segment.h"
38 : #include "../dom/select.h"
39 : #include "../dom/staff.h"
40 : #include "../dom/stafftype.h"
41 :
42 : using namespace mu::engraving;
43 :
44 : //---------------------------------------------------------
45 : // slashFill
46 : /// fills selected region with slashes
47 : //---------------------------------------------------------
48 :
49 1 : void EditSlashNotation::slashFill(Transaction&, Score* score)
50 : {
51 1 : staff_idx_t startStaff = score->selection().staffStart();
52 1 : staff_idx_t endStaff = score->selection().staffEnd();
53 1 : Segment* startSegment = score->selection().startSegment();
54 1 : if (!startSegment) { // empty score?
55 0 : return;
56 : }
57 :
58 1 : Segment* endSegment = score->selection().endSegment();
59 :
60 : // operate on measures underlying mmrests
61 1 : if (startSegment && startSegment->measure() && startSegment->measure()->isMMRest()) {
62 0 : startSegment = startSegment->measure()->mmRestFirst()->first();
63 : }
64 1 : if (endSegment && endSegment->measure() && endSegment->measure()->isMMRest()) {
65 0 : endSegment = endSegment->measure()->mmRestLast()->last();
66 : }
67 :
68 1 : Fraction endTick = endSegment ? endSegment->tick() : score->lastSegment()->tick() + Fraction::eps();
69 1 : Chord* firstSlash = 0;
70 1 : Chord* lastSlash = 0;
71 :
72 : // loop through staves in selection
73 3 : for (staff_idx_t staffIdx = startStaff; staffIdx < endStaff; ++staffIdx) {
74 2 : track_idx_t track = staffIdx * VOICES;
75 2 : voice_idx_t voice = muse::nidx;
76 : // loop through segments adding slashes on each beat
77 36 : for (Segment* s = startSegment; s && s->tick() < endTick; s = s->next1()) {
78 34 : if (s->segmentType() != SegmentType::ChordRest) {
79 6 : continue;
80 : }
81 : // determine beat type based on time signature
82 28 : int d = s->measure()->timesig().denominator();
83 28 : int n = (d > 4 && s->measure()->timesig().numerator() % 3 == 0) ? 3 : 1;
84 28 : Fraction f(n, d);
85 : // skip over any leading segments before next (first) beat
86 28 : if (s->rtick().ticks() % f.ticks()) {
87 0 : continue;
88 : }
89 : // determine voice to use - first available voice for this measure / staff
90 28 : if (voice == muse::nidx || s->rtick().isZero()) {
91 : bool needGap[VOICES];
92 11 : for (voice = 0; voice < VOICES; ++voice) {
93 11 : needGap[voice] = false;
94 11 : ChordRest* cr = toChordRest(s->element(track + voice));
95 : // no chordrest == treat as ordinary rest for purpose of determining availability of voice
96 : // but also, we will need to make a gap for this voice if we do end up choosing it
97 11 : if (!cr) {
98 3 : needGap[voice] = true;
99 : }
100 : // chord == keep looking for an available voice
101 8 : else if (cr->isChord()) {
102 2 : continue;
103 : }
104 : // full measure rest == OK to use voice
105 6 : else if (cr->durationType() == DurationType::V_MEASURE) {
106 4 : break;
107 : }
108 : // no chordrest or ordinary rest == OK to use voice
109 : // if there are nothing but rests for duration of measure / selection
110 5 : bool ok = true;
111 9 : for (Segment* ns = s->next(SegmentType::ChordRest); ns && ns != endSegment; ns = ns->next(SegmentType::ChordRest)) {
112 5 : ChordRest* ncr = toChordRest(ns->element(track + voice));
113 5 : if (ncr && ncr->isChord()) {
114 1 : ok = false;
115 1 : break;
116 : }
117 : }
118 5 : if (ok) {
119 4 : break;
120 : }
121 : }
122 : // no available voices, just use voice 0
123 8 : if (voice == VOICES) {
124 0 : voice = 0;
125 : }
126 : // no cr was found in segment for this voice, so make gap
127 8 : if (needGap[voice]) {
128 3 : score->makeGapVoice(s, track + voice, f, s->tick());
129 : }
130 : }
131 : // construct note
132 28 : int line = 0;
133 28 : bool error = false;
134 28 : NoteVal nv;
135 28 : if (score->staff(staffIdx)->staffType(s->tick())->group() == StaffGroup::TAB) {
136 0 : line = score->staff(staffIdx)->lines(s->tick()) / 2;
137 : } else {
138 28 : line = score->staff(staffIdx)->middleLine(s->tick()); // staff(staffIdx)->lines() - 1;
139 : }
140 28 : if (score->staff(staffIdx)->staffType(s->tick())->group() == StaffGroup::PERCUSSION) {
141 14 : nv.pitch = 0;
142 14 : nv.headGroup = NoteHeadGroup::HEAD_SLASH;
143 : } else {
144 14 : Position p;
145 14 : p.segment = s;
146 14 : p.staffIdx = staffIdx;
147 14 : p.line = line;
148 14 : p.fret = INVALID_FRET_INDEX;
149 14 : score->inputState().setRest(false); // needed for tab
150 14 : nv = NoteInput::noteValForPosition(score, p, AccidentalType::NONE, error);
151 : }
152 28 : if (error) {
153 0 : continue;
154 : }
155 : // insert & turn into slash
156 28 : s = score->setNoteRest(s, track + voice, nv, f);
157 28 : Chord* c = toChord(s->element(track + voice));
158 28 : if (c) {
159 28 : if (c->links()) {
160 0 : for (EngravingObject* e : *c->links()) {
161 0 : Chord* lc = toChord(e);
162 0 : lc->setSlash(true, true);
163 : }
164 : } else {
165 28 : c->setSlash(true, true);
166 : }
167 : }
168 28 : lastSlash = c;
169 28 : if (!firstSlash) {
170 1 : firstSlash = c;
171 : }
172 : }
173 : }
174 :
175 : // re-select the slashes
176 1 : score->deselectAll();
177 1 : if (firstSlash && lastSlash) {
178 1 : score->select(firstSlash, SelectType::RANGE);
179 1 : score->select(lastSlash, SelectType::RANGE);
180 : }
181 : }
182 :
183 : //---------------------------------------------------------
184 : // slashRhythm
185 : /// converts rhythms in selected region to slashes
186 : //---------------------------------------------------------
187 :
188 1 : void EditSlashNotation::slashRhythm(Transaction&, Score* score)
189 : {
190 1 : std::set<Chord*> chords;
191 : // loop through all notes in selection
192 130 : for (EngravingItem* e : score->selection().elements()) {
193 129 : if (e->voice() >= 2 && e->isRest()) {
194 2 : Rest* r = toRest(e);
195 2 : if (r->links()) {
196 0 : for (EngravingObject* se : *r->links()) {
197 0 : Rest* lr = toRest(se);
198 0 : lr->setAccent(!lr->accent());
199 : }
200 : } else {
201 2 : r->setAccent(!r->accent());
202 : }
203 2 : continue;
204 129 : } else if (e->isNote()) {
205 46 : Note* n = toNote(e);
206 46 : if (n->noteType() != NoteType::NORMAL) {
207 0 : continue;
208 : }
209 46 : Chord* c = n->chord();
210 : // check for duplicates (chords with multiple notes)
211 46 : if (muse::contains(chords, c)) {
212 0 : continue;
213 : }
214 46 : chords.insert(c);
215 : // toggle slash setting
216 46 : c->setSlash(!c->slash(), false);
217 : }
218 : }
219 1 : }
|