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 : /**
24 : \file
25 : Implementation of class VelocityMap.
26 : */
27 :
28 : #include "velocitymap.h"
29 :
30 : #include <cmath>
31 : #include <cassert>
32 :
33 : #include "containers.h"
34 :
35 : using namespace mu;
36 :
37 : namespace mu::engraving {
38 : //---------------------------------------------------------
39 : // interpolateVelocity
40 : /// the maths looks complex, but is just a series of graph transformations.
41 : /// You can see these graphically at: https://www.desmos.com/calculator/kk89ficmjk
42 : //---------------------------------------------------------
43 :
44 1946 : int VelocityMap::interpolate(Fraction& eventTick, VelocityEvent& event, Fraction& tick)
45 : {
46 1946 : assert(event.m_type == VelocityEventType::HAIRPIN);
47 :
48 : // Prevent zero-division error
49 1946 : if (event.m_cachedStartVal == event.m_cachedEndVal || event.m_length.isZero()) {
50 0 : return event.m_cachedStartVal;
51 : }
52 :
53 : // Ticks to change expression over
54 1946 : int exprTicks = event.m_length.ticks();
55 1946 : int exprDiff = event.m_cachedEndVal - event.m_cachedStartVal;
56 :
57 1946 : std::function<int(int)> valueFunction;
58 1946 : switch (event.m_method) {
59 0 : case ChangeMethod::EXPONENTIAL:
60 : // Due to the nth-root, exponential functions do not flip with negative values, and cause errors,
61 : // so treat it as a piecewise function.
62 0 : if (exprDiff > 0) {
63 0 : valueFunction = [&](int ct) {
64 : return int(
65 0 : pow(
66 0 : pow((exprDiff + 1), 1.0 / double(exprTicks)), // the exprTicks root of d+1
67 : double(ct) // to the power of the current tick (exponential)
68 0 : ) - 1
69 0 : );
70 0 : };
71 : } else {
72 0 : valueFunction = [&](int ct) {
73 0 : return -int(
74 0 : pow(
75 0 : pow((-exprDiff + 1), 1.0 / double(exprTicks)), // the exprTicks root of 1-d
76 : double(ct) // again to the power of ct
77 0 : ) + 1
78 0 : );
79 0 : };
80 : }
81 0 : break;
82 : // Uses sin x transformed, which _does_ flip with negative numbers
83 0 : case ChangeMethod::EASE_IN_OUT:
84 0 : valueFunction = [&](int ct) {
85 : return int(
86 0 : (double(exprDiff) / 2.0) * (
87 0 : sin(
88 0 : double(ct) * (
89 0 : double(M_PI / double(exprTicks))
90 : ) - double(M_PI / 2.0)
91 0 : ) + 1
92 : )
93 0 : );
94 0 : };
95 0 : break;
96 0 : case ChangeMethod::EASE_IN:
97 0 : valueFunction = [&](int ct) {
98 : return int(
99 0 : double(exprDiff) * (
100 0 : sin(
101 0 : double(ct - double(exprTicks)) * (
102 0 : double(M_PI / double(2 * exprTicks))
103 : )
104 0 : ) + 1
105 : )
106 0 : );
107 0 : };
108 0 : break;
109 0 : case ChangeMethod::EASE_OUT:
110 0 : valueFunction = [&](int ct) {
111 : return int(
112 0 : double(exprDiff) * sin(
113 0 : double(ct) * (
114 0 : double(M_PI / double(2 * exprTicks))
115 : )
116 : )
117 0 : );
118 0 : };
119 0 : break;
120 1946 : case ChangeMethod::NORMAL:
121 : default:
122 5838 : valueFunction = [&](int ct) {
123 : return int(
124 1946 : double(exprDiff) * (double(ct) / double(exprTicks))
125 1946 : );
126 1946 : };
127 1946 : break;
128 : }
129 :
130 1946 : return event.m_cachedStartVal + valueFunction(tick.ticks() - eventTick.ticks());
131 1946 : }
132 :
133 : //---------------------------------------------------------
134 : // val
135 : /// return value at tick position.
136 : //---------------------------------------------------------
137 :
138 2540 : int VelocityMap::val(Fraction tick) const
139 : {
140 2540 : auto eventIter = upper_bound(tick);
141 2540 : if (eventIter == begin()) {
142 264 : return DEFAULT_VALUE;
143 : }
144 :
145 2276 : eventIter--;
146 2276 : bool foundHairpin = false;
147 2276 : VelocityEvent hairpinFound = eventIter->second; // only used to init
148 2276 : Fraction hairpinFoundStartTick = eventIter->first;
149 2276 : auto values = equal_range(hairpinFoundStartTick);
150 6498 : for (auto it = values.first; it != values.second; ++it) {
151 4222 : auto& event = it->second;
152 4222 : if (event.m_type == VelocityEventType::HAIRPIN) {
153 1946 : foundHairpin = true;
154 1946 : hairpinFound = event;
155 : }
156 : }
157 :
158 2276 : if (!foundHairpin) {
159 : // Last event must be a dynamic, since there are max two events at one tick
160 330 : return eventIter->second.m_value;
161 : }
162 :
163 1946 : if (tick >= (hairpinFoundStartTick + hairpinFound.m_length)) {
164 0 : return hairpinFound.m_cachedEndVal;
165 : } else {
166 : // Do some maths!
167 1946 : return interpolate(hairpinFoundStartTick, hairpinFound, tick); // NOTE:JT check should hairpinFound be eventIter.value()
168 : }
169 : }
170 :
171 : //---------------------------------------------------------
172 : // addDynamic
173 : //---------------------------------------------------------
174 :
175 332 : void VelocityMap::addDynamic(Fraction tick, int value)
176 : {
177 332 : insert({ tick, VelocityEvent(value) });
178 332 : }
179 :
180 : //---------------------------------------------------------
181 : // addHairpin
182 : /// A `change` of 0 means that the change in velocity should be calculated from the next dynamic
183 : /// velocity event.
184 : //---------------------------------------------------------
185 :
186 32 : void VelocityMap::addHairpin(Fraction stick, Fraction etick, int change, ChangeMethod method, ChangeDirection direction)
187 : {
188 32 : change = std::abs(change);
189 32 : change *= (direction == ChangeDirection::INCREASING) ? 1 : -1;
190 32 : insert({ stick, VelocityEvent(stick, etick, change, method, direction) });
191 32 : }
192 :
193 : //---------------------------------------------------------
194 : // sortHairpins
195 : /// put the hairpins in size order if they start at the same point
196 : //---------------------------------------------------------
197 :
198 1864 : void VelocityMap::sortHairpins()
199 : {
200 2176 : for (auto& tick : muse::uniqueKeys(*this)) {
201 : // hairpinEvents will contain all the hairpins at this tick
202 312 : std::vector<VelocityEvent> hairpinEvents;
203 312 : auto values = this->equal_range(tick);
204 660 : for (auto it = values.first; it != values.second; ++it) {
205 348 : auto& event = it->second;
206 348 : if (event.m_type == VelocityEventType::DYNAMIC) {
207 316 : continue;
208 : }
209 32 : hairpinEvents.push_back(event);
210 : }
211 :
212 312 : if (int(hairpinEvents.size()) > 1) {
213 : // Sort hairpinEvents so that the longest hairpins come first -
214 : // this is important for when we remove hairpins/dynamics enclosed within other
215 : // hairpins during stage 1.
216 0 : std::sort(hairpinEvents.begin(), hairpinEvents.end(), VelocityMap::compareHairpinEvents);
217 0 : for (auto& event : hairpinEvents) {
218 0 : insert({ tick, event });
219 : }
220 : }
221 2176 : }
222 1864 : }
223 :
224 : //---------------------------------------------------------
225 : // resolveHairpinCollisions
226 : /// remove any hairpins that are completely enclosed within other hairpins
227 : //---------------------------------------------------------
228 :
229 1864 : void VelocityMap::resolveHairpinCollisions()
230 : {
231 1864 : Fraction currentHairpinEnd = Fraction(-1, 1); // end point of hairpin we're in
232 1864 : bool inHairpin = false; // whether we're in a hairpin or not
233 :
234 : // Keep a record of the endpoints
235 1864 : EndPointsVector endPoints;
236 1864 : std::vector<bool> startsInHairpin;
237 :
238 1864 : auto i = begin();
239 2212 : while (i != end()) {
240 348 : Fraction tick = i->first;
241 348 : VelocityEvent& event = i->second;
242 348 : Fraction etick = tick + event.m_length;
243 :
244 : // Reset if we've left the hairpin we were in
245 348 : if (currentHairpinEnd < tick) {
246 320 : inHairpin = false;
247 : }
248 :
249 348 : if (event.m_type == VelocityEventType::HAIRPIN) {
250 32 : if (inHairpin) {
251 0 : if (etick <= currentHairpinEnd) {
252 : // delete, this event is enveloped
253 0 : i = erase(i);
254 : // don't add to the end points
255 0 : continue;
256 : } else {
257 0 : currentHairpinEnd = etick;
258 0 : startsInHairpin.push_back(true);
259 : }
260 : } else {
261 32 : currentHairpinEnd = etick;
262 32 : inHairpin = true;
263 32 : startsInHairpin.push_back(false);
264 : }
265 :
266 32 : endPoints.push_back(std::make_pair(tick, etick));
267 : }
268 :
269 348 : i++;
270 : }
271 :
272 1864 : adjustCollidingHairpinsLength(startsInHairpin, endPoints);
273 1864 : }
274 :
275 : //---------------------------------------------------------
276 : // adjustCollidingHairpinsLength
277 : /// readjust lengths of any colliding hairpins
278 : //---------------------------------------------------------
279 :
280 1864 : void VelocityMap::adjustCollidingHairpinsLength(std::vector<bool>& startsInHairpin, EndPointsVector& endPoints)
281 : {
282 : // moveTo stores the events that need to be moved to a Fraction position
283 1864 : std::map<Fraction, VelocityEvent> moveTo;
284 1864 : auto i = begin();
285 1864 : int j = -1;
286 2212 : while (i != end()) {
287 348 : Fraction tick = i->first;
288 348 : VelocityEvent& event = i->second;
289 348 : if (event.m_type != VelocityEventType::HAIRPIN) {
290 316 : i++;
291 348 : continue;
292 : }
293 :
294 32 : j++;
295 32 : if (!startsInHairpin[j]) {
296 32 : i++;
297 32 : continue;
298 : }
299 :
300 : // Take a copy of the event and remove it
301 0 : Fraction newTick = endPoints[j - 1].second;
302 0 : event.m_length -= (newTick - tick);
303 0 : moveTo[newTick] = event;
304 0 : i = erase(i);
305 : }
306 :
307 : // Re-insert the events that we need to move in their new positions
308 1864 : for (auto k = moveTo.begin(); k != moveTo.end(); k++) {
309 0 : insert({ k->first, k->second });
310 : }
311 1864 : }
312 :
313 : //---------------------------------------------------------
314 : // resolveDynamicInsideHairpinCollisions
315 : /// if dynamic is inside the hairpin, shorten hairpin length up to this dynamic
316 : //---------------------------------------------------------
317 :
318 1864 : void VelocityMap::resolveDynamicInsideHairpinCollisions()
319 : {
320 1864 : auto lastEventIt = end();
321 1864 : auto i = begin();
322 2212 : while (i != end()) {
323 348 : VelocityEvent& event = i->second;
324 316 : if (event.m_type == VelocityEventType::DYNAMIC && lastEventIt != end()
325 664 : && lastEventIt->second.m_type == VelocityEventType::HAIRPIN) {
326 28 : Fraction dynamicTick = i->first;
327 28 : Fraction lastHairpinStart = lastEventIt->first;
328 28 : Fraction lastHairpinEnd = lastHairpinStart + lastEventIt->second.m_length;
329 28 : if (lastHairpinEnd > dynamicTick) {
330 4 : lastEventIt->second.m_length = dynamicTick - lastHairpinStart;
331 : }
332 : }
333 :
334 348 : lastEventIt = i;
335 348 : i++;
336 : }
337 1864 : }
338 :
339 32 : bool VelocityMap::dynamicExistsOnTick(Fraction tick) const
340 : {
341 32 : auto values = equal_range(tick);
342 32 : for (auto it = values.first; it != values.second; ++it) {
343 24 : auto& event = it->second;
344 24 : if (event.m_type == VelocityEventType::DYNAMIC) {
345 24 : return true;
346 : }
347 : }
348 :
349 8 : return false;
350 : }
351 :
352 56 : VelocityEvent VelocityMap::dynamicEventForTick(Fraction tick) const
353 : {
354 56 : auto equalValues = equal_range(tick);
355 60 : for (auto it = equalValues.first; it != equalValues.second; ++it) {
356 56 : auto& event = it->second;
357 56 : if (event.m_type == VelocityEventType::DYNAMIC) {
358 52 : return event;
359 : }
360 : }
361 :
362 4 : if (equalValues.first == begin()) {
363 0 : return VelocityEvent();
364 : }
365 :
366 4 : auto previousIt = std::prev(equalValues.first);
367 4 : auto smallerValues = equal_range(previousIt->first);
368 4 : for (auto it = smallerValues.first; it != smallerValues.second; ++it) {
369 4 : auto& event = it->second;
370 4 : if (event.m_type == VelocityEventType::DYNAMIC) {
371 4 : return event;
372 : }
373 : }
374 :
375 0 : return VelocityEvent();
376 : }
377 :
378 1864 : void VelocityMap::addMissingDynamicsAfterHairpins()
379 : {
380 20 : auto nextDynamicVal = [](int previousDynamicValue, ChangeDirection hairpinDirection) {
381 20 : int newVal
382 : = (hairpinDirection
383 20 : == ChangeDirection::INCREASING ? previousDynamicValue + VelocityMap::STEP : previousDynamicValue - VelocityMap::STEP);
384 20 : return std::clamp(newVal, VelocityMap::MIN_VALUE, VelocityMap::MAX_VALUE);
385 : };
386 :
387 2220 : for (auto it = begin(); it != end(); it++) {
388 356 : auto& event = it->second;
389 356 : if (event.m_type == VelocityEventType::HAIRPIN && event.m_value == 0) {
390 32 : Fraction startHairpinTick = it->first;
391 32 : Fraction endHairpinTick = it->first + it->second.m_length;
392 32 : VelocityEvent dynamicOnCurrentTick = dynamicEventForTick(startHairpinTick);
393 :
394 32 : if (!dynamicExistsOnTick(endHairpinTick)) {
395 8 : if (dynamicOnCurrentTick.m_type != VelocityEventType::INVALID) {
396 8 : int newVal = nextDynamicVal(dynamicOnCurrentTick.m_value, event.m_direction);
397 8 : insert({ endHairpinTick, VelocityEvent(newVal) });
398 : }
399 : } else {
400 24 : VelocityEvent dynamicOnEndTick = dynamicEventForTick(endHairpinTick);
401 24 : int velocityJump = dynamicOnEndTick.m_value - dynamicOnCurrentTick.m_value;
402 :
403 24 : if ((event.m_direction
404 16 : == ChangeDirection::INCREASING && velocityJump < 0)
405 16 : || (event.m_direction == ChangeDirection::DECREASING && velocityJump > 0)) {
406 12 : int newVal = nextDynamicVal(dynamicOnCurrentTick.m_value, event.m_direction);
407 12 : event.m_value = newVal - dynamicOnCurrentTick.m_value;
408 : }
409 : }
410 : }
411 : }
412 1864 : }
413 :
414 : //---------------------------------------------------------
415 : // fillHairpinsCache
416 : /// cache start and end values for each hairpin
417 : //---------------------------------------------------------
418 :
419 1864 : void VelocityMap::fillHairpinsCache()
420 : {
421 2220 : for (auto i = begin(); i != end(); i++) {
422 356 : Fraction tick = i->first;
423 356 : auto& event = i->second;
424 356 : if (event.m_type != VelocityEventType::HAIRPIN) {
425 324 : continue;
426 : }
427 :
428 : // Phase 1: cache a start value for the hairpin
429 : // Try and get a dynamic at the tick of this hairpin
430 32 : bool foundDynamic = false;
431 32 : auto values = this->equal_range(tick);
432 36 : for (auto it = values.first; it != values.second; ++it) {
433 32 : auto& currentChangeEvent = it->second;
434 32 : if (currentChangeEvent.m_type == VelocityEventType::DYNAMIC) {
435 28 : event.m_cachedStartVal = currentChangeEvent.m_value;
436 28 : foundDynamic = true;
437 28 : break;
438 : }
439 : }
440 :
441 : // If there isn't a dynamic, use from the last event:
442 : // - the cached end value if it's a hairpin
443 : // - the value if it's a dynamic
444 32 : if (!foundDynamic) {
445 4 : if (i != begin()) {
446 4 : auto prevChangeEventIter = i;
447 4 : prevChangeEventIter--;
448 :
449 : // Look for a hairpin first
450 4 : bool foundHairpin = false;
451 4 : auto prevValues = this->equal_range(prevChangeEventIter->first);
452 8 : for (auto it = prevValues.first; it != prevValues.second; ++it) {
453 4 : auto& prevChangeEvent = it->second;
454 4 : if (prevChangeEvent.m_type == VelocityEventType::HAIRPIN) {
455 0 : event.m_cachedStartVal = prevChangeEvent.m_cachedEndVal;
456 0 : foundHairpin = true;
457 0 : break;
458 : }
459 : }
460 :
461 4 : if (!foundHairpin) {
462 : // prevChangeEventIter must point to a dynamic in this case
463 4 : event.m_cachedStartVal = prevChangeEventIter->second.m_value;
464 : }
465 : } else {
466 0 : event.m_cachedStartVal = DEFAULT_VALUE;
467 : }
468 : }
469 :
470 : // Phase 2: cache an end value for the hairpin
471 : // If there's no set velocity change:
472 32 : if (event.m_value == 0) {
473 20 : auto nextChangeEventIter = i;
474 20 : nextChangeEventIter++;
475 : // There's a chance that the next event is a dynamic at the same tick as the
476 : // start of the current hairpin. If so, get the next event, which is assured
477 : // to be a different (larger) tick
478 20 : if (nextChangeEventIter != end() && nextChangeEventIter->first == tick) {
479 0 : nextChangeEventIter++;
480 : }
481 :
482 : // If this is the last event, there is no change
483 20 : if (nextChangeEventIter == end()) {
484 0 : event.m_cachedEndVal = event.m_cachedStartVal;
485 : } else {
486 : // Search for a dynamic event at the next event point
487 20 : bool foundDynamic2 = false;
488 20 : auto nextValues = this->equal_range(nextChangeEventIter->first);
489 20 : for (auto it = nextValues.first; it != nextValues.second; ++it) {
490 20 : auto& nextChangeEvent = it->second;
491 20 : if (nextChangeEvent.m_type == VelocityEventType::DYNAMIC) {
492 20 : event.m_cachedEndVal = nextChangeEvent.m_value;
493 20 : foundDynamic2 = true;
494 20 : break;
495 : }
496 : }
497 :
498 : // We haven't found a dynamic, so there must be a hairpin. What does the user want?
499 : // A good guess would to be to interpolate, but that might get complex, so just ignore
500 : // this hairpin and set the ending to be the same as the start.
501 : // TODO: implementing some form of smart interpolation would be nice.
502 20 : if (!foundDynamic2) {
503 0 : event.m_cachedEndVal = event.m_cachedStartVal;
504 : }
505 : }
506 : } else {
507 12 : event.m_cachedEndVal = event.m_cachedStartVal + event.m_value;
508 : }
509 :
510 : // And finally... if something's wrong, make it not wrong
511 32 : if ((event.m_cachedStartVal > event.m_cachedEndVal && event.m_direction == ChangeDirection::INCREASING)
512 32 : || (event.m_cachedStartVal < event.m_cachedEndVal && event.m_direction == ChangeDirection::DECREASING)) {
513 0 : event.m_cachedEndVal = event.m_cachedStartVal;
514 : }
515 : }
516 1864 : }
517 :
518 : //---------------------------------------------------------
519 : // setup
520 : //---------------------------------------------------------
521 :
522 1864 : void VelocityMap::setup()
523 : {
524 1864 : sortHairpins();
525 1864 : resolveHairpinCollisions();
526 1864 : resolveDynamicInsideHairpinCollisions();
527 1864 : addMissingDynamicsAfterHairpins();
528 1864 : fillHairpinsCache();
529 1864 : }
530 :
531 : //---------------------------------------------------------
532 : // changesInRange
533 : /// returns a list of changes in a range, and their start and end points
534 : //---------------------------------------------------------
535 :
536 168 : std::vector<std::pair<Fraction, Fraction> > VelocityMap::changesInRange(Fraction stick, Fraction etick) const
537 : {
538 168 : std::vector<std::pair<Fraction, Fraction> > tempChanges;
539 :
540 : // Force a new event on every noteon, in case the velocity has changed
541 168 : tempChanges.push_back(std::make_pair(stick, stick));
542 185 : for (auto iter = lower_bound(stick); iter != end(); iter++) {
543 75 : Fraction tick = iter->first;
544 75 : if (tick > etick) {
545 58 : break;
546 : }
547 :
548 17 : auto& event = iter->second;
549 17 : if (event.m_type == VelocityEventType::DYNAMIC) {
550 16 : tempChanges.push_back(std::make_pair(tick, tick));
551 1 : } else if (event.m_type == VelocityEventType::HAIRPIN) {
552 1 : Fraction eventEtick = tick + event.m_length;
553 1 : Fraction useEtick = eventEtick > etick ? etick : eventEtick;
554 1 : tempChanges.push_back(std::make_pair(tick, useEtick));
555 : }
556 : }
557 :
558 : // And also go back one and try to find hairpin coming into this range
559 168 : auto iter = lower_bound(stick);
560 168 : if (iter != begin()) {
561 36 : iter--;
562 36 : auto& event = iter->second;
563 36 : if (event.m_type == VelocityEventType::HAIRPIN) {
564 0 : Fraction eventEtick = iter->first + event.m_length;
565 0 : if (eventEtick > stick) {
566 0 : tempChanges.push_back(std::make_pair(stick, eventEtick));
567 : }
568 : }
569 : }
570 168 : return tempChanges;
571 0 : }
572 :
573 : //---------------------------------------------------------
574 : // operator==
575 : //---------------------------------------------------------
576 :
577 0 : bool VelocityEvent::operator==(const VelocityEvent& event) const
578 : {
579 : return
580 0 : m_value == event.m_value
581 0 : && m_type == event.m_type
582 0 : && m_length == event.m_length
583 0 : && m_method == event.m_method
584 0 : && m_direction == event.m_direction
585 : ;
586 : }
587 : }
|