1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Rosegarden
5 A MIDI and audio sequencer and musical notation editor.
6 Copyright 2000-2021 the Rosegarden development team.
7
8 Other copyrights also apply to some parts of this work. Please
9 see the AUTHORS file and individual file headers for details.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License as
13 published by the Free Software Foundation; either version 2 of the
14 License, or (at your option) any later version. See the file
15 COPYING included with this distribution for more information.
16 */
17
18
19 #include "InterpretCommand.h"
20
21 #include "base/Composition.h"
22 #include "base/Event.h"
23 #include "base/NotationTypes.h"
24 #include "misc/Debug.h"
25 #include "base/Quantizer.h"
26 #include "base/Segment.h"
27 #include "base/Sets.h"
28 #include "base/BaseProperties.h"
29 #include "base/Selection.h"
30 #include "document/BasicSelectionCommand.h"
31 #include <QString>
32
33
34 namespace Rosegarden
35 {
36
37 using namespace BaseProperties;
38
39 const int InterpretCommand::NoInterpretation = 0;
40 const int InterpretCommand::GuessDirections = (1<<0);
41 const int InterpretCommand::ApplyTextDynamics = (1<<1);
42 const int InterpretCommand::ApplyHairpins = (1<<2);
43 const int InterpretCommand::StressBeats = (1<<3);
44 const int InterpretCommand::Articulate = (1<<4);
45 const int InterpretCommand::AllInterpretations= (1<<5) - 1;
46
~InterpretCommand()47 InterpretCommand::~InterpretCommand()
48 {
49 for (IndicationMap::iterator i = m_indications.begin();
50 i != m_indications.end(); ++i) {
51 delete i->second;
52 }
53 }
54
55 void
modifySegment()56 InterpretCommand::modifySegment()
57 {
58 // Of all the interpretations, Articulate is the only one that
59 // changes event times or durations. This means we must apply it
60 // last, as the selection cannot be used after it's been applied,
61 // because the events in the selection will have been recreated
62 // with their new timings.
63
64 // The default velocity for new notes is 100, and the range is
65 // 0-127 (in practice this seems to be roughly logarithmic rather
66 // than linear, though perhaps that's an illusion).
67
68 // We should only apply interpretation to those events actually
69 // selected, but when applying things like hairpins and text
70 // dynamics we need to take into account all dynamics that may
71 // cover our events even if they're not selected or are not within
72 // the time range of the selection at all. So first we'd better
73 // find all the likely indications, starting at (for the sake of
74 // argument) three bars before the start of the selection:
75
76 Segment &segment(getSegment());
77
78 timeT t = m_selection->getStartTime();
79 for (int i = 0; i < 3; ++i)
80 t = segment.getBarStartForTime(t);
81
82 Segment::iterator itr = segment.findTime(t);
83
84 while (itr != segment.end()) {
85 timeT eventTime = (*itr)->getAbsoluteTime();
86 if (eventTime > m_selection->getEndTime())
87 break;
88 if ((*itr)->isa(Indication::EventType)) {
89 m_indications[eventTime] = new Indication(**itr);
90 }
91 ++itr;
92 }
93
94 //!!! need the option of ignoring current velocities or adjusting
95 //them: at the moment ApplyTextDynamics ignores them and the
96 //others adjust them
97
98 if (m_interpretations & GuessDirections)
99 guessDirections();
100 if (m_interpretations & ApplyTextDynamics)
101 applyTextDynamics();
102 if (m_interpretations & ApplyHairpins)
103 applyHairpins();
104 if (m_interpretations & StressBeats)
105 stressBeats();
106 if (m_interpretations & Articulate)
107 articulate();
108
109 //!!! Finally, in future we should extend this to allow
110 // indications on one segment (e.g. top line of piano staff) to
111 // affect another (e.g. bottom line). All together now: "Even
112 // X11 Rosegarden could do that!"
113 }
114
115 void
guessDirections()116 InterpretCommand::guessDirections()
117 {
118 //...
119 }
120
121 void
applyTextDynamics()122 InterpretCommand::applyTextDynamics()
123 {
124 // laborious
125
126 Segment &segment(getSegment());
127 int velocity = 100;
128
129 timeT startTime = m_selection->getStartTime();
130 timeT endTime = m_selection->getEndTime();
131
132 for (Segment::iterator i = segment.begin();
133 segment.isBeforeEndMarker(i); ++i) {
134
135 timeT t = (*i)->getAbsoluteTime();
136
137 if (t > endTime)
138 break;
139
140 if (Text::isTextOfType(*i, Text::Dynamic)) {
141
142 std::string text;
143 if ((*i)->get
144 <String>(Text::TextPropertyName, text)) {
145 velocity = getVelocityForDynamic(text);
146 }
147 }
148
149 if (t >= startTime &&
150 (*i)->isa(Note::EventType) && m_selection->contains(*i)) {
151 (*i)->set
152 <Int>(VELOCITY, velocity);
153 }
154 }
155 }
156
157 int
getVelocityForDynamic(std::string text)158 InterpretCommand::getVelocityForDynamic(std::string text)
159 {
160 int velocity = 100;
161
162 // should do case-insensitive matching with whitespace
163 // removed. can surely be cleverer about this too!
164
165 if (text == "ppppp")
166 velocity = 10;
167 else if (text == "pppp")
168 velocity = 20;
169 else if (text == "ppp")
170 velocity = 30;
171 else if (text == "pp")
172 velocity = 40;
173 else if (text == "p")
174 velocity = 60;
175 else if (text == "mp")
176 velocity = 80;
177 else if (text == "mf")
178 velocity = 90;
179 else if (text == "f")
180 velocity = 105;
181 else if (text == "ff")
182 velocity = 110;
183 else if (text == "fff")
184 velocity = 115;
185 else if (text == "ffff")
186 velocity = 120;
187 else if (text == "fffff")
188 velocity = 125;
189 else if (text == "d5")
190 velocity = 5;
191 else if (text == "d10")
192 velocity = 10;
193 else if (text == "d15")
194 velocity = 15;
195 else if (text == "d20")
196 velocity = 20;
197 else if (text == "d25")
198 velocity = 25;
199 else if (text == "d30")
200 velocity = 30;
201 else if (text == "d35")
202 velocity = 35;
203 else if (text == "d40")
204 velocity = 40;
205 else if (text == "d45")
206 velocity = 45;
207 else if (text == "d50")
208 velocity = 50;
209 else if (text == "d55")
210 velocity = 55;
211 else if (text == "d60")
212 velocity = 60;
213 else if (text == "d65")
214 velocity = 65;
215 else if (text == "d70")
216 velocity = 70;
217 else if (text == "d75")
218 velocity = 75;
219 else if (text == "d80")
220 velocity = 80;
221 else if (text == "d85")
222 velocity = 85;
223 else if (text == "d90")
224 velocity = 90;
225 else if (text == "d95")
226 velocity = 95;
227 else if (text == "d100")
228 velocity = 100;
229 else if (text == "d105")
230 velocity = 105;
231 else if (text == "d110")
232 velocity = 110;
233 else if (text == "d115")
234 velocity = 115;
235 else if (text == "d120")
236 velocity = 120;
237 else if (text == "d125")
238 velocity = 125;
239
240 NOTATION_DEBUG << "InterpretCommand::getVelocityForDynamic: unrecognised dynamic " << text;
241
242 return velocity;
243 }
244
245 void
applyHairpins()246 InterpretCommand::applyHairpins()
247 {
248 Segment &segment(getSegment());
249 int velocityToApply = -1;
250
251 for (EventSelection::eventcontainer::iterator ecitr =
252 m_selection->getSegmentEvents().begin();
253 ecitr != m_selection->getSegmentEvents().end(); ++ecitr) {
254
255 Event *e = *ecitr;
256 if (Text::isTextOfType(e, Text::Dynamic)) {
257 velocityToApply = -1;
258 }
259 if (!e->isa(Note::EventType))
260 continue;
261 bool crescendo = true;
262
263 IndicationMap::iterator inditr =
264 findEnclosingIndication(e, Indication::Crescendo);
265
266 // we can't be in both crescendo and decrescendo -- at least,
267 // not meaningfully
268
269 if (inditr == m_indications.end()) {
270 inditr = findEnclosingIndication(e, Indication::Decrescendo);
271 if (inditr == m_indications.end()) {
272 if (velocityToApply > 0) {
273 e->set
274 <Int>(VELOCITY, velocityToApply);
275 }
276 continue;
277 }
278 crescendo = false;
279 }
280
281 // The starting velocity for the indication is easy -- it's
282 // just the velocity of the last note at or before the
283 // indication begins that has a velocity
284
285 timeT hairpinStartTime = inditr->first;
286 // ensure we scan all of the events at this time:
287 Segment::iterator itr(segment.findTime(hairpinStartTime + 1));
288 while (itr == segment.end() ||
289 (*itr)->getAbsoluteTime() > hairpinStartTime ||
290 !(*itr)->isa(Note::EventType) ||
291 !(*itr)->has(VELOCITY)) {
292 if (itr == segment.begin()) {
293 itr = segment.end();
294 break;
295 }
296 --itr;
297 }
298
299 long startingVelocity = 100;
300 if (itr != segment.end()) {
301 (*itr)->get
302 <Int>(VELOCITY, startingVelocity);
303 }
304
305 // The ending velocity is harder. If there's a dynamic change
306 // directly after the hairpin, then we want to use that
307 // dynamic's velocity unless it opposes the hairpin's
308 // direction. If there isn't, or it does oppose the hairpin,
309 // we should probably make the degree of change caused by the
310 // hairpin depend on its total duration.
311
312 long endingVelocity = startingVelocity;
313 timeT hairpinEndTime = inditr->first +
314 inditr->second->getIndicationDuration();
315 itr = segment.findTime(hairpinEndTime);
316 while (itr != segment.end()) {
317 if (Text::isTextOfType(*itr, Text::Dynamic)) {
318 std::string text;
319 if ((*itr)->get
320 <String>(Text::TextPropertyName, text)) {
321 endingVelocity = getVelocityForDynamic(text);
322 break;
323 }
324 }
325 if ((*itr)->getAbsoluteTime() >
326 (hairpinEndTime + Note(Note::Crotchet).getDuration()))
327 break;
328 ++itr;
329 }
330
331 if (( crescendo && (endingVelocity < startingVelocity)) ||
332 (!crescendo && (endingVelocity > startingVelocity))) {
333 // we've got it wrong; prefer following the hairpin to
334 // following whatever direction we got the dynamic from
335 endingVelocity = startingVelocity;
336 // and then fall into the next conditional to set up the
337 // velocities
338 }
339
340 if (endingVelocity == startingVelocity) {
341 // calculate an ending velocity based on starting velocity
342 // and hairpin duration (okay, we'll leave that bit for later)
343 endingVelocity = startingVelocity * (crescendo ? 120 : 80) / 100;
344 }
345
346 double proportion =
347 (double(e->getAbsoluteTime() - hairpinStartTime) /
348 double(hairpinEndTime - hairpinStartTime));
349 long velocity =
350 int((endingVelocity - startingVelocity) * proportion +
351 startingVelocity);
352
353 NOTATION_DEBUG << "InterpretCommand::applyHairpins: velocity of note at " << e->getAbsoluteTime() << " is " << velocity << " (" << proportion << " through hairpin from " << startingVelocity << " to " << endingVelocity << ")";
354 if (velocity < 10)
355 velocity = 10;
356 if (velocity > 127)
357 velocity = 127;
358 e->set
359 <Int>(VELOCITY, velocity);
360 velocityToApply = velocity;
361 }
362 }
363
364 void
stressBeats()365 InterpretCommand::stressBeats()
366 {
367 Composition *c = getSegment().getComposition();
368
369 for (EventSelection::eventcontainer::iterator itr =
370 m_selection->getSegmentEvents().begin();
371 itr != m_selection->getSegmentEvents().end(); ++itr) {
372
373 Event *e = *itr;
374 if (!e->isa(Note::EventType))
375 continue;
376
377 timeT t = e->getNotationAbsoluteTime();
378 TimeSignature timeSig = c->getTimeSignatureAt(t);
379 timeT barStart = getSegment().getBarStartForTime(t);
380 int stress = timeSig.getEmphasisForTime(t - barStart);
381
382 // stresses are from 0 to 4, so we add 12% to the velocity
383 // at the maximum stress, subtract 4% at the minimum
384 int velocityChange = stress * 4 - 4;
385
386 // do this even if velocityChange == 0, in case the event
387 // has no velocity yet
388 long velocity = 100;
389 e->get
390 <Int>(VELOCITY, velocity);
391 velocity += velocity * velocityChange / 100;
392 if (velocity < 10)
393 velocity = 10;
394 if (velocity > 127)
395 velocity = 127;
396 e->set
397 <Int>(VELOCITY, velocity);
398 }
399 }
400
401 void
articulate()402 InterpretCommand::articulate()
403 {
404 // Basic articulations:
405 //
406 // -- Anything marked tenuto or within a slur gets 100% of its
407 // nominal duration (that's what we need the quantizer for,
408 // to get the display nominal duration), and its velocity
409 // is unchanged.
410 //
411 // -- Anything marked marcato gets 60%, or 70% if slurred (!),
412 // and gets an extra 15% of velocity.
413 //
414 // -- Anything marked staccato gets 55%, or 70% if slurred,
415 // and unchanged velocity.
416 //
417 // -- Anything marked staccatissimo gets 30%, or 50% if slurred (!),
418 // and loses 5% of velocity.
419 //
420 // -- Anything marked sforzando gains 35% of velocity.
421 //
422 // -- Anything marked with an accent gains 30% of velocity.
423 //
424 // -- Anything marked rinforzando gains 15% of velocity and has
425 // its full duration. Guess we really need to use some proper
426 // controllers here.
427 //
428 // -- Anything marked down-bow gains 5% of velocity, anything
429 // marked up-bow loses 5%.
430 //
431 // -- Anything unmarked and unslurred, or marked tenuto and
432 // slurred, gets 90% of duration.
433
434 std::set
435 <Event *> toErase;
436 std::set
437 <Event *> toInsert;
438 Segment &segment(getSegment());
439
440 for (EventSelection::eventcontainer::iterator ecitr =
441 m_selection->getSegmentEvents().begin();
442 ecitr != m_selection->getSegmentEvents().end(); ++ecitr) {
443
444 Event *e = *ecitr;
445 if (!e->isa(Note::EventType))
446 continue;
447 Segment::iterator itr = segment.findSingle(e);
448 Chord chord(segment, itr, m_quantizer);
449
450 // the things that affect duration
451 bool staccato = false;
452 bool staccatissimo = false;
453 bool marcato = false;
454 bool tenuto = false;
455 bool rinforzando = false;
456 bool slurred = false;
457
458 int velocityChange = 0;
459
460 std::vector<Mark> marks(chord.getMarksForChord());
461
462 for (std::vector<Mark>::iterator i = marks.begin();
463 i != marks.end(); ++i) {
464
465 if (*i == Marks::Accent) {
466 velocityChange += 30;
467 } else if (*i == Marks::Tenuto) {
468 tenuto = true;
469 } else if (*i == Marks::Staccato) {
470 staccato = true;
471 } else if (*i == Marks::Staccatissimo) {
472 staccatissimo = true;
473 velocityChange -= 5;
474 } else if (*i == Marks::Marcato) {
475 marcato = true;
476 velocityChange += 15;
477 } else if (*i == Marks::Sforzando) {
478 velocityChange += 35;
479 } else if (*i == Marks::Rinforzando) {
480 rinforzando = true;
481 velocityChange += 15;
482 } else if (*i == Marks::DownBow) {
483 velocityChange += 5;
484 } else if (*i == Marks::UpBow) {
485 velocityChange -= 5;
486 }
487 }
488
489 IndicationMap::iterator inditr =
490 findEnclosingIndication(e, Indication::Slur);
491
492 if (inditr != m_indications.end())
493 slurred = true;
494 if (slurred) {
495 // last note in a slur should be treated as if unslurred
496 timeT slurEnd =
497 inditr->first + inditr->second->getIndicationDuration();
498 if (slurEnd == e->getNotationAbsoluteTime() + e->getNotationDuration() ||
499 slurEnd == e->getAbsoluteTime() + e->getDuration()) {
500 slurred = false;
501 }
502 /*!!!
503 Segment::iterator slurEndItr = segment.findTime(slurEnd);
504 if (slurEndItr != segment.end() &&
505 (*slurEndItr)->getNotationAbsoluteTime() <=
506 e->getNotationAbsoluteTime()) {
507 slurred = false;
508 }
509 */
510 }
511
512 int durationChange = 0;
513
514 if (slurred) {
515 //!!! doesn't seem to be picking up slurs correctly
516 if (tenuto)
517 durationChange = -10;
518 else if (marcato || staccato)
519 durationChange = -30;
520 else if (staccatissimo)
521 durationChange = -50;
522 else
523 durationChange = 0;
524 } else {
525 if (tenuto)
526 durationChange = 0;
527 else if (marcato)
528 durationChange = -40;
529 else if (staccato)
530 durationChange = -45;
531 else if (staccatissimo)
532 durationChange = -70;
533 else if (rinforzando)
534 durationChange = 0;
535 else
536 durationChange = -10;
537 }
538
539 NOTATION_DEBUG << "InterpretCommand::modifySegment: chord has " << chord.size() << " notes in it";
540
541 for (Chord::iterator ci = chord.begin();
542 ci != chord.end(); ++ci) {
543
544 e = **ci;
545
546 NOTATION_DEBUG << "InterpretCommand::modifySegment: For note at " << e->getAbsoluteTime() << ", velocityChange is " << velocityChange << " and durationChange is " << durationChange;
547
548 // do this even if velocityChange == 0, in case the event
549 // has no velocity yet
550 long velocity = 100;
551 e->get
552 <Int>(VELOCITY, velocity);
553 velocity += velocity * velocityChange / 100;
554 if (velocity < 10)
555 velocity = 10;
556 if (velocity > 127)
557 velocity = 127;
558 e->set
559 <Int>(VELOCITY, velocity);
560
561 timeT duration = e->getNotationDuration();
562
563 // don't mess with the duration of a tied note
564 bool tied = false;
565 e->get<Bool>(TIED_FORWARD, tied);
566 if (!tied)
567 e->get<Bool>(TIED_BACKWARD, tied);
568 if (tied) {
569 durationChange = 0;
570 NOTATION_DEBUG << "InterpretCommand::modifySegment: Tied "
571 << (e->has(TIED_FORWARD) ? "for" : "back")
572 << "ward note encountered, durationChange is "
573 << durationChange;
574 }
575
576 timeT newDuration = duration + duration * durationChange / 100;
577
578 // this comparison instead of "durationChange != 0"
579 // because we want to permit the possibility of resetting
580 // the performance duration of a note (that's perhaps been
581 // articulated wrongly) based on the notation duration:
582
583 timeT eventDuration = e->getDuration();
584
585 if (eventDuration != newDuration) {
586
587 NOTATION_DEBUG << "InterpretCommand::modifySegment: durationChange is " << durationChange << "; e->getDuration() is " << eventDuration << "; newDuration is " << newDuration;
588
589 if (toErase.find(e) == toErase.end()) {
590
591 //!!! deal with tuplets
592
593 Event *newEvent = new Event(*e,
594 e->getAbsoluteTime(),
595 newDuration,
596 e->getSubOrdering(),
597 e->getNotationAbsoluteTime(),
598 duration);
599 toInsert.insert(newEvent);
600 toErase.insert(e);
601 }
602 }
603 }
604
605 // what we want to do here is jump our iterator to the final
606 // element in the chord -- but that doesn't work because we're
607 // iterating through the selection, not the segment. So for
608 // now we just accept the fact that notes in chords might be
609 // processed multiple times (slow) and added into the toErase
610 // set more than once (hence the nasty tests in the loop just
611 // after the close of this loop).
612 }
613
614 for (std::set
615 <Event *>::iterator j = toErase.begin(); j != toErase.end(); ++j) {
616 Segment::iterator jtr(segment.findSingle(*j));
617 if (jtr != segment.end())
618 segment.erase(jtr);
619 }
620
621 for (std::set
622 <Event *>::iterator j = toInsert.begin(); j != toInsert.end(); ++j) {
623 segment.insert(*j);
624 }
625 }
626
627 InterpretCommand::IndicationMap::iterator
628
findEnclosingIndication(Event * e,std::string type)629 InterpretCommand::findEnclosingIndication(Event *e,
630 std::string type)
631 {
632 // a bit slow, but let's wait and see whether it's a bottleneck
633 // before we worry about that
634
635 timeT t = e->getAbsoluteTime();
636 IndicationMap::iterator itr = m_indications.lower_bound(t);
637
638 while (1) {
639 if (itr != m_indications.end()) {
640
641 if (itr->second->getIndicationType() == type &&
642 itr->first <= t &&
643 itr->first + itr->second->getIndicationDuration() > t) {
644 return itr;
645 }
646 }
647 if (itr == m_indications.begin())
648 break;
649 --itr;
650 }
651
652 return m_indications.end();
653 }
654
655 }
656