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