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 : /* TO DO:
24 : - XML export
25 :
26 : NICE-TO-HAVE TODO:
27 : - draggable handles of glissando segments
28 : - re-attachable glissando extrema (with [Shift]+arrows, use SlurSegment::edit()
29 : and SlurSegment::changeAnchor() in slur.cpp as models)
30 : */
31 :
32 : #include "glissando.h"
33 :
34 : #include <cmath>
35 : #include <algorithm>
36 :
37 : #include "types/typesconv.h"
38 :
39 : #include "chord.h"
40 : #include "harppedaldiagram.h"
41 : #include "measure.h"
42 : #include "note.h"
43 : #include "part.h"
44 : #include "score.h"
45 : #include "segment.h"
46 : #include "staff.h"
47 : #include "system.h"
48 : #include "utils.h"
49 :
50 : #include "log.h"
51 :
52 : using namespace mu;
53 : using namespace mu::engraving;
54 :
55 : namespace mu::engraving {
56 : static const ElementStyle glissandoElementStyle {
57 : { Sid::glissandoFontFace, Pid::FONT_FACE },
58 : { Sid::glissandoFontSize, Pid::FONT_SIZE },
59 : { Sid::glissandoFontStyle, Pid::FONT_STYLE },
60 : { Sid::glissandoLineWidth, Pid::LINE_WIDTH },
61 : { Sid::glissandoShowText, Pid::GLISS_SHOW_TEXT },
62 : { Sid::glissandoText, Pid::GLISS_TEXT },
63 : { Sid::glissandoStyle, Pid::GLISS_STYLE },
64 : { Sid::glissandoLineStyle, Pid::LINE_STYLE },
65 : { Sid::glissandoDashLineLen, Pid::DASH_LINE_LEN },
66 : { Sid::glissandoDashGapLen, Pid::DASH_GAP_LEN },
67 : { Sid::glissandoType, Pid::GLISS_TYPE }
68 : };
69 :
70 : //=========================================================
71 : // GlissandoSegment
72 : //=========================================================
73 :
74 160 : GlissandoSegment::GlissandoSegment(Glissando* sp)
75 160 : : LineSegment(ElementType::GLISSANDO_SEGMENT, sp, ElementFlag::MOVABLE)
76 : {
77 160 : }
78 :
79 : //---------------------------------------------------------
80 : // propertyDelegate
81 : //---------------------------------------------------------
82 :
83 262 : EngravingObject* GlissandoSegment::propertyDelegate(Pid pid) const
84 : {
85 262 : switch (pid) {
86 0 : case Pid::GLISS_TYPE:
87 : case Pid::GLISS_TEXT:
88 : case Pid::GLISS_SHOW_TEXT:
89 : case Pid::GLISS_STYLE:
90 : case Pid::GLISS_SHIFT:
91 : case Pid::GLISS_EASEIN:
92 : case Pid::GLISS_EASEOUT:
93 : case Pid::FONT_FACE:
94 : case Pid::FONT_SIZE:
95 : case Pid::FONT_STYLE:
96 : case Pid::LINE_WIDTH:
97 0 : return glissando();
98 262 : default:
99 262 : return LineSegment::propertyDelegate(pid);
100 : }
101 : }
102 :
103 : //=========================================================
104 : // Glissando
105 : //=========================================================
106 :
107 171 : Glissando::Glissando(DummyParentOr<EngravingItem> parent)
108 171 : : SLine(ElementType::GLISSANDO, parent, ElementFlag::MOVABLE)
109 : {
110 171 : initElementStyle(&glissandoElementStyle);
111 :
112 : static const std::array<Pid, 4> propertiesToInitialise {
113 : Pid::GLISS_SHIFT,
114 : Pid::GLISS_EASEIN,
115 : Pid::GLISS_EASEOUT,
116 : Pid::DIAGONAL
117 : };
118 :
119 855 : for (const Pid& pid : propertiesToInitialise) {
120 684 : resetProperty(pid);
121 : }
122 171 : }
123 :
124 8 : Glissando::Glissando(const Glissando& g)
125 8 : : SLine(g)
126 : {
127 8 : _text = g._text;
128 8 : _fontFace = g._fontFace;
129 8 : _fontSize = g._fontSize;
130 8 : _glissandoType = g._glissandoType;
131 8 : _glissandoStyle = g._glissandoStyle;
132 8 : _glissandoShift = g._glissandoShift;
133 8 : _easeIn = g._easeIn;
134 8 : _easeOut = g._easeOut;
135 8 : _showText = g._showText;
136 8 : _fontStyle = g._fontStyle;
137 8 : m_isHarpGliss = g.m_isHarpGliss;
138 8 : }
139 :
140 : //---------------------------------------------------------
141 : // createLineSegment
142 : //---------------------------------------------------------
143 :
144 160 : LineSegment* Glissando::createLineSegment()
145 : {
146 160 : GlissandoSegment* seg = new GlissandoSegment(this);
147 160 : seg->setTrack(track());
148 160 : seg->setColor(color());
149 160 : return seg;
150 : }
151 :
152 2104 : Sid Glissando::getPropertyStyle(Pid id) const
153 : {
154 2104 : if (id == Pid::GLISS_STYLE) {
155 279 : return isHarpGliss().value_or(false) ? Sid::glissandoStyleHarp : Sid::glissandoStyle;
156 : }
157 :
158 1825 : return SLine::getPropertyStyle(id);
159 : }
160 :
161 10 : bool Glissando::pitchSteps(const Spanner* spanner, std::vector<int>& pitchOffsets)
162 : {
163 10 : if (!spanner->endElement()->isNote()) {
164 0 : return false;
165 : }
166 10 : const Glissando* glissando = toGlissando(spanner);
167 10 : if (!glissando->playSpanner()) {
168 0 : return false;
169 : }
170 10 : GlissandoStyle glissandoStyle = glissando->glissandoStyle();
171 10 : if (glissandoStyle == GlissandoStyle::PORTAMENTO) {
172 0 : return false;
173 : }
174 : // only consider glissando connected to NOTE.
175 10 : const Note* noteStart = toNote(spanner->startElement());
176 10 : const Note* noteEnd = toNote(spanner->endElement());
177 10 : int pitchStart = noteStart->ppitch();
178 10 : int pitchEnd = noteEnd->ppitch();
179 10 : if (pitchEnd == pitchStart) {
180 0 : return false;
181 : }
182 10 : int direction = pitchEnd > pitchStart ? 1 : -1;
183 10 : pitchOffsets.clear();
184 10 : if (glissandoStyle == GlissandoStyle::DIATONIC) {
185 : // Obey harp pedal diagrams if on a harp staff
186 2 : if (glissando->isHarpGliss().value_or(false)) {
187 2 : HarpPedalDiagram* hd = spanner->originPart()->currentHarpDiagram(spanner->tick());
188 2 : std::set<int> playableTpcs = hd ? hd->playableTpcs() : std::set<int>({ 14, 16, 18, 13, 15, 17, 19 });
189 2 : std::vector<int> playablePitches;
190 2 : playablePitches.reserve(playableTpcs.size());
191 16 : for (int t : playableTpcs) {
192 14 : playablePitches.push_back(tpc2pitch(t) % PITCH_DELTA_OCTAVE);
193 : }
194 :
195 : // Push starting note, then check for enharmonic on the next string. If there is an enharmonic, 0 will be pushed back twice
196 2 : pitchOffsets.push_back(0);
197 2 : int en = noteStart->tpc() + TPC_DELTA_ENHARMONIC * -direction;
198 : // Harp pedalling will only have 1 flat or sharp
199 2 : if (en >= TPC_F_B && en <= TPC_B_S && playableTpcs.find(en) != playableTpcs.end()) {
200 1 : pitchOffsets.push_back(0);
201 : }
202 :
203 18 : for (int p = pitchStart + direction; p != pitchEnd; p += direction) {
204 : // Count times pitch occurs in harp pedalling - this accounts for enharmonics
205 16 : int pitchOccurrences = std::count(playablePitches.begin(), playablePitches.end(), p % PITCH_DELTA_OCTAVE);
206 16 : if (pitchOccurrences > 0) {
207 7 : pitchOffsets.insert(pitchOffsets.end(), pitchOccurrences, p - pitchStart);
208 : }
209 : }
210 :
211 : // Check for enharmonic at end, in correct direction
212 2 : en = noteEnd->tpc() + TPC_DELTA_ENHARMONIC * direction;
213 2 : if (en >= TPC_F_B && en <= TPC_B_S && playableTpcs.find(en) != playableTpcs.end()) {
214 2 : pitchOffsets.push_back(pitchEnd - pitchStart);
215 : }
216 :
217 2 : return pitchOffsets.size() > 0;
218 2 : }
219 :
220 : // Regular diatonic mode
221 0 : int lineStart = noteStart->line();
222 : // scale obeying accidentals
223 0 : for (int line = lineStart, pitch = pitchStart; (direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd); line -= direction) {
224 0 : int halfSteps = chromaticPitchSteps(noteStart, noteEnd, lineStart - line);
225 0 : pitch = pitchStart + halfSteps;
226 0 : if ((direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd)) {
227 0 : pitchOffsets.push_back(halfSteps);
228 : }
229 : }
230 0 : return pitchOffsets.size() > 0;
231 : }
232 8 : if (glissandoStyle == GlissandoStyle::CHROMATIC) {
233 41 : for (int pitch = pitchStart; pitch != pitchEnd; pitch += direction) {
234 33 : pitchOffsets.push_back(pitch - pitchStart);
235 : }
236 8 : return true;
237 : }
238 0 : static const std::vector<bool> whiteNotes = { true, false, true, false, true, true, false, true, false, true, false, true };
239 0 : int Cnote = 60; // pitch of middle C
240 0 : bool notePick = glissandoStyle == GlissandoStyle::WHITE_KEYS;
241 0 : for (int pitch = pitchStart; pitch != pitchEnd; pitch += direction) {
242 0 : int idx = ((pitch - Cnote) + 1200) % 12;
243 0 : if (whiteNotes[idx] == notePick) {
244 0 : pitchOffsets.push_back(pitch - pitchStart);
245 : }
246 : }
247 0 : return true;
248 : }
249 :
250 : //---------------------------------------------------------
251 : // STATIC FUNCTIONS: guessInitialNote
252 : //
253 : // Used while reading old scores (either 1.x or transitional 2.0) to determine (guess!)
254 : // the glissando initial note from its final chord. Returns the top note of previous chord
255 : // of the same instrument, preferring the chord in the same track as chord, if it exists.
256 : //
257 : // CANNOT be called if the final chord and/or its segment do not exist yet in the score
258 : //
259 : // Parameter: chord: the chord this glissando ends into
260 : // Returns: the top note in a suitable previous chord or nullptr if none found.
261 : //---------------------------------------------------------
262 :
263 0 : Note* Glissando::guessInitialNote(Chord* chord)
264 : {
265 0 : switch (chord->noteType()) {
266 : // case NoteType::INVALID:
267 : // return 0;
268 : // for grace notes before, previous chord is previous chord of parent chord
269 0 : case NoteType::ACCIACCATURA:
270 : case NoteType::APPOGGIATURA:
271 : case NoteType::GRACE4:
272 : case NoteType::GRACE16:
273 : case NoteType::GRACE32:
274 : // move unto parent chord and proceed to standard case
275 0 : if (chord->ownershipParent() && chord->ownershipParent()->isChord()) {
276 0 : chord = toChord(chord->ownershipParent());
277 : } else {
278 0 : return 0;
279 : }
280 0 : break;
281 : // for grace notes after, return top note of parent chord
282 0 : case NoteType::GRACE8_AFTER:
283 : case NoteType::GRACE16_AFTER:
284 : case NoteType::GRACE32_AFTER:
285 0 : if (chord->ownershipParent() && chord->ownershipParent()->isChord()) {
286 0 : return toChord(chord->ownershipParent())->upNote();
287 : } else { // no parent or parent is not a chord?
288 0 : return nullptr;
289 : }
290 0 : case NoteType::NORMAL:
291 : {
292 : // if chord has grace notes before, the last one is the previous note
293 0 : std::vector<Chord*> graces = chord->graceNotesBefore();
294 0 : if (graces.size() > 0) {
295 0 : return graces.back()->upNote();
296 : }
297 0 : }
298 0 : break; // else process to standard case
299 0 : default:
300 0 : break;
301 : }
302 :
303 : // standard case (NORMAL or grace before chord)
304 :
305 : // if parent not a segment, can't locate a target note
306 0 : if (!chord->ownershipParent()->isSegment()) {
307 0 : return 0;
308 : }
309 :
310 0 : track_idx_t chordTrack = chord->track();
311 0 : Segment* segm = chord->segment();
312 0 : Part* part = chord->part();
313 0 : if (segm != nullptr) {
314 0 : segm = segm->prev1();
315 : }
316 0 : while (segm) {
317 : // if previous segment is a ChordRest segment
318 0 : if (segm->segmentType() == SegmentType::ChordRest) {
319 0 : Chord* target = nullptr;
320 : // look for a Chord in the same track
321 0 : if (segm->element(chordTrack) && segm->element(chordTrack)->isChord()) {
322 0 : target = toChord(segm->element(chordTrack));
323 : } else { // if no same track, look for other chords in the same instrument
324 0 : for (EngravingItem* currChord : segm->elist()) {
325 0 : if (currChord && currChord->isChord() && toChord(currChord)->part() == part) {
326 0 : target = toChord(currChord);
327 0 : break;
328 : }
329 : }
330 : }
331 : // if we found a target previous chord
332 0 : if (target) {
333 : // if chord has grace notes after, the last one is the previous note
334 0 : std::vector<Chord*> graces = target->graceNotesAfter();
335 0 : if (graces.size() > 0) {
336 0 : return graces.back()->upNote();
337 : }
338 0 : return target->upNote(); // if no grace after, return top note
339 0 : }
340 : }
341 0 : segm = segm->prev1();
342 : }
343 0 : LOGD("no first note for glissando found");
344 0 : return 0;
345 : }
346 :
347 : //---------------------------------------------------------
348 : // getProperty
349 : //---------------------------------------------------------
350 :
351 1015 : PropertyValue Glissando::getProperty(Pid propertyId) const
352 : {
353 1015 : switch (propertyId) {
354 0 : case Pid::GLISS_TYPE:
355 0 : return glissandoType();
356 13 : case Pid::GLISS_TEXT:
357 13 : return text();
358 0 : case Pid::GLISS_SHOW_TEXT:
359 0 : return showText();
360 1 : case Pid::GLISS_STYLE:
361 1 : return glissandoStyle();
362 77 : case Pid::GLISS_SHIFT:
363 77 : return glissandoShift();
364 77 : case Pid::GLISS_EASEIN:
365 77 : return easeIn();
366 77 : case Pid::GLISS_EASEOUT:
367 77 : return easeOut();
368 0 : case Pid::FONT_FACE:
369 0 : return _fontFace;
370 0 : case Pid::FONT_SIZE:
371 0 : return _fontSize;
372 0 : case Pid::FONT_STYLE:
373 0 : return int(_fontStyle);
374 770 : default:
375 770 : break;
376 : }
377 770 : return SLine::getProperty(propertyId);
378 : }
379 :
380 : //---------------------------------------------------------
381 : // setProperty
382 : //---------------------------------------------------------
383 :
384 2875 : bool Glissando::setProperty(Pid propertyId, const PropertyValue& v)
385 : {
386 2875 : switch (propertyId) {
387 177 : case Pid::GLISS_TYPE:
388 177 : setGlissandoType(v.value<GlissandoType>());
389 177 : break;
390 248 : case Pid::GLISS_TEXT:
391 248 : setText(v.value<String>());
392 248 : break;
393 175 : case Pid::GLISS_SHOW_TEXT:
394 175 : setShowText(v.toBool());
395 175 : break;
396 271 : case Pid::GLISS_STYLE:
397 : {
398 : // Make sure harp glisses can only be diatonic and chromatic
399 271 : GlissandoStyle glissStyle = v.value<GlissandoStyle>();
400 271 : if (isHarpGliss().value_or(false) && (glissStyle != GlissandoStyle::DIATONIC && glissStyle != GlissandoStyle::CHROMATIC)) {
401 0 : glissStyle = GlissandoStyle::DIATONIC;
402 : }
403 271 : setGlissandoStyle(glissStyle);
404 271 : break;
405 : }
406 171 : case Pid::GLISS_SHIFT:
407 171 : setGlissandoShift(v.toBool());
408 171 : break;
409 171 : case Pid::GLISS_EASEIN:
410 171 : setEaseIn(v.toInt());
411 171 : break;
412 171 : case Pid::GLISS_EASEOUT:
413 171 : setEaseOut(v.toInt());
414 171 : break;
415 175 : case Pid::FONT_FACE:
416 175 : setFontFace(v.value<String>());
417 175 : break;
418 175 : case Pid::FONT_SIZE:
419 175 : setFontSize(v.toReal());
420 175 : break;
421 175 : case Pid::FONT_STYLE:
422 175 : setFontStyle(FontStyle(v.toInt()));
423 175 : break;
424 966 : default:
425 966 : if (!SLine::setProperty(propertyId, v)) {
426 0 : return false;
427 : }
428 966 : break;
429 : }
430 2875 : triggerLayout();
431 2875 : return true;
432 : }
433 :
434 : //---------------------------------------------------------
435 : // propertyDefault
436 : //---------------------------------------------------------
437 :
438 1868 : PropertyValue Glissando::propertyDefault(Pid propertyId) const
439 : {
440 1868 : switch (propertyId) {
441 0 : case Pid::GLISS_TYPE:
442 0 : return style().styleV(Sid::glissandoType);
443 0 : case Pid::GLISS_SHOW_TEXT:
444 0 : return true;
445 93 : case Pid::GLISS_STYLE:
446 93 : return style().styleV(getPropertyStyle(propertyId));
447 248 : case Pid::GLISS_SHIFT:
448 248 : return false;
449 496 : case Pid::GLISS_EASEIN:
450 : case Pid::GLISS_EASEOUT:
451 496 : return 0;
452 90 : case Pid::GLISS_TEXT:
453 90 : return style().styleV(Sid::glissandoText);
454 171 : case Pid::DIAGONAL:
455 171 : return true;
456 770 : default:
457 770 : break;
458 : }
459 770 : return SLine::propertyDefault(propertyId);
460 : }
461 :
462 0 : TranslatableString Glissando::subtypeUserName() const
463 : {
464 0 : return TConv::userName(glissandoType());
465 : }
466 : }
|