Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2025 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 : #pragma once
23 :
24 : #include <variant>
25 : #include <vector>
26 :
27 : #include "async/channel.h"
28 : #include "realfn.h"
29 : #include "global/types/number.h"
30 : #include "types/flags.h"
31 : #include "types/number.h"
32 : #include "types/sharedmap.h"
33 :
34 : #include "automationpoint.h"
35 : #include "mpetypes.h"
36 : #include "playbacksetupdata.h"
37 :
38 : namespace muse::mpe {
39 : struct ArrangementContext
40 : {
41 : timestamp_t nominalTimestamp = 0;
42 : timestamp_t actualTimestamp = 0;
43 : duration_t nominalDuration = 0;
44 : duration_t actualDuration = 0;
45 : voice_layer_idx_t voiceLayerIndex = 0;
46 : staff_layer_idx_t staffLayerIndex = 0;
47 : double bps = 0.0;
48 :
49 0 : bool hasStart() const
50 : {
51 0 : return actualDuration > 0;
52 : }
53 :
54 0 : bool hasEnd() const
55 : {
56 0 : return actualDuration != mpe::INFINITE_DURATION;
57 : }
58 :
59 2 : bool operator==(const ArrangementContext& other) const
60 : {
61 2 : return nominalTimestamp == other.nominalTimestamp
62 2 : && actualTimestamp == other.actualTimestamp
63 2 : && nominalDuration == other.nominalDuration
64 2 : && actualDuration == other.actualDuration
65 2 : && voiceLayerIndex == other.voiceLayerIndex
66 2 : && staffLayerIndex == other.staffLayerIndex
67 4 : && muse::is_equal(bps, other.bps);
68 : }
69 : };
70 :
71 : struct PitchContext
72 : {
73 : pitch_level_t nominalPitchLevel = 0;
74 : PitchCurve pitchCurve;
75 :
76 6 : bool operator==(const PitchContext& other) const
77 : {
78 6 : return nominalPitchLevel == other.nominalPitchLevel
79 6 : && pitchCurve == other.pitchCurve;
80 : }
81 : };
82 :
83 : struct ExpressionContext
84 : {
85 : ArticulationMap articulations;
86 : dynamic_level_t nominalDynamicLevel = MIN_DYNAMIC_LEVEL;
87 : ExpressionCurve expressionCurve;
88 : std::optional<float> velocityOverride;
89 :
90 6 : bool operator==(const ExpressionContext& other) const
91 : {
92 6 : return articulations == other.articulations
93 3 : && nominalDynamicLevel == other.nominalDynamicLevel
94 3 : && expressionCurve == other.expressionCurve
95 9 : && velocityOverride == velocityOverride;
96 : }
97 : };
98 :
99 : struct NoteEvent
100 : {
101 1 : NoteEvent() = default;
102 :
103 2 : explicit NoteEvent(ArrangementContext&& arrangementCtx,
104 : PitchContext&& pitchCtx,
105 : ExpressionContext&& expressionCtx)
106 2 : : m_arrangementCtx(arrangementCtx),
107 2 : m_pitchCtx(pitchCtx),
108 2 : m_expressionCtx(expressionCtx)
109 : {
110 2 : }
111 :
112 2393 : explicit NoteEvent(const timestamp_t nominalTimestamp,
113 : const duration_t nominalDuration,
114 : const voice_layer_idx_t voiceIdx,
115 : const staff_layer_idx_t staffIdx,
116 : const pitch_level_t nominalPitchLevel,
117 : const dynamic_level_t nominalDynamicLevel,
118 : const ArticulationMap& articulationsApplied,
119 : const double bps,
120 : const float requiredVelocityFraction = 0.f,
121 : const PitchCurve& requiredPitchCurve = {})
122 2393 : {
123 2393 : m_arrangementCtx.nominalDuration = nominalDuration;
124 2393 : m_arrangementCtx.nominalTimestamp = nominalTimestamp;
125 2393 : m_arrangementCtx.voiceLayerIndex = voiceIdx;
126 2393 : m_arrangementCtx.staffLayerIndex = staffIdx;
127 2393 : m_arrangementCtx.bps = bps;
128 :
129 2393 : m_pitchCtx.nominalPitchLevel = nominalPitchLevel;
130 :
131 2393 : m_expressionCtx.articulations = articulationsApplied;
132 2393 : m_expressionCtx.nominalDynamicLevel = nominalDynamicLevel;
133 :
134 2393 : setUp(requiredVelocityFraction, requiredPitchCurve);
135 2393 : }
136 :
137 3002 : const ArrangementContext& arrangementCtx() const
138 : {
139 3002 : return m_arrangementCtx;
140 : }
141 :
142 423 : const PitchContext& pitchCtx() const
143 : {
144 423 : return m_pitchCtx;
145 : }
146 :
147 1202 : const ExpressionContext& expressionCtx() const
148 : {
149 1202 : return m_expressionCtx;
150 : }
151 :
152 1 : bool operator==(const NoteEvent& other) const
153 : {
154 1 : return m_arrangementCtx == other.m_arrangementCtx
155 1 : && m_pitchCtx == other.m_pitchCtx
156 2 : && m_expressionCtx == other.m_expressionCtx;
157 : }
158 :
159 : private:
160 :
161 : template<typename T>
162 5834 : inline T mult(T v, float f)
163 : {
164 5834 : return static_cast<T>(static_cast<float>(v) * f);
165 : }
166 :
167 2393 : void setUp(const float requiredVelocityFraction, const PitchCurve& requiredPitchCurve)
168 : {
169 2393 : calculateActualDuration(m_expressionCtx.articulations);
170 2393 : calculateActualTimestamp(m_expressionCtx.articulations);
171 :
172 2393 : if (requiredPitchCurve.empty()) {
173 2383 : calculatePitchCurve(m_expressionCtx.articulations);
174 : } else {
175 10 : m_pitchCtx.pitchCurve = requiredPitchCurve;
176 : }
177 :
178 2393 : calculateExpressionCurve(m_expressionCtx.articulations, requiredVelocityFraction);
179 2393 : }
180 :
181 2393 : void calculateActualTimestamp(const ArticulationMap& articulationsApplied)
182 : {
183 2393 : m_arrangementCtx.actualTimestamp = m_arrangementCtx.nominalTimestamp;
184 :
185 2393 : if (articulationsApplied.empty()) {
186 9 : return;
187 : }
188 :
189 2384 : timestamp_t timestampOffsetValue = mult(m_arrangementCtx.nominalDuration,
190 : percentageToFactor(articulationsApplied.averageTimestampOffset()));
191 :
192 2384 : m_arrangementCtx.actualTimestamp += timestampOffsetValue;
193 : }
194 :
195 2393 : void calculateActualDuration(const ArticulationMap& articulationsApplied)
196 : {
197 2393 : m_arrangementCtx.actualDuration = m_arrangementCtx.nominalDuration;
198 :
199 2393 : if (articulationsApplied.empty() || m_arrangementCtx.nominalDuration == INFINITE_DURATION) {
200 9 : return;
201 : }
202 :
203 2384 : m_arrangementCtx.actualDuration = mult(m_arrangementCtx.actualDuration,
204 : percentageToFactor(articulationsApplied.averageDurationFactor()));
205 : }
206 :
207 2383 : void calculatePitchCurve(const ArticulationMap& articulationsApplied)
208 : {
209 2383 : m_pitchCtx.pitchCurve = articulationsApplied.averagePitchOffsetMap();
210 :
211 2383 : pitch_level_t averagePitchRange = articulationsApplied.averagePitchRange();
212 2383 : if (averagePitchRange == 0 || averagePitchRange == PITCH_LEVEL_STEP) {
213 2359 : return;
214 : }
215 :
216 24 : float ratio = static_cast<float>(averagePitchRange) / static_cast<float>(PITCH_LEVEL_STEP);
217 24 : float patternUnitRatio = PITCH_LEVEL_STEP / static_cast<float>(ONE_PERCENT);
218 :
219 288 : for (auto& pair : m_pitchCtx.pitchCurve) {
220 264 : pair.second = static_cast<pitch_level_t>(RealRound(static_cast<float>(pair.second) * ratio * patternUnitRatio, 0));
221 : }
222 : }
223 :
224 2393 : void calculateExpressionCurve(const ArticulationMap& articulationsApplied, const float requiredVelocityFraction)
225 : {
226 2393 : m_expressionCtx.expressionCurve = articulationsApplied.averageDynamicOffsetMap();
227 :
228 2393 : if (!RealIsNull(requiredVelocityFraction)) {
229 0 : m_expressionCtx.velocityOverride = requiredVelocityFraction;
230 : }
231 :
232 2393 : if (m_expressionCtx.expressionCurve.empty()) {
233 1327 : return;
234 : }
235 :
236 1066 : dynamic_level_t articulationDynamicLevel = articulationsApplied.averageMaxAmplitudeLevel();
237 1066 : dynamic_level_t nominalDynamicLevel = m_expressionCtx.nominalDynamicLevel;
238 :
239 1066 : constexpr dynamic_level_t naturalDynamicLevel = dynamicLevelFromType(DynamicType::Natural);
240 :
241 1066 : float dynamicAmplifyFactor = static_cast<float>(articulationDynamicLevel - naturalDynamicLevel) / DYNAMIC_LEVEL_STEP;
242 :
243 1066 : dynamic_level_t amplificationDiff = mult(std::max(articulationsApplied.averageDynamicRange(), DYNAMIC_LEVEL_STEP),
244 : dynamicAmplifyFactor);
245 :
246 1066 : dynamic_level_t actualDynamicLevel = nominalDynamicLevel + amplificationDiff;
247 :
248 1066 : if (actualDynamicLevel == articulationDynamicLevel) {
249 833 : return;
250 : }
251 :
252 233 : float ratio = static_cast<float>(actualDynamicLevel) / static_cast<float>(articulationDynamicLevel);
253 :
254 2796 : for (auto& pair : m_expressionCtx.expressionCurve) {
255 2563 : pair.second = static_cast<dynamic_level_t>(RealRound(pair.second * ratio, 0));
256 : }
257 : }
258 :
259 : ArrangementContext m_arrangementCtx;
260 : PitchContext m_pitchCtx;
261 : ExpressionContext m_expressionCtx;
262 : };
263 :
264 : struct ControllerChangeEvent {
265 : enum Type : signed char {
266 : Undefined = -1,
267 : Modulation,
268 : SustainPedalOnOff,
269 : PitchBend,
270 : };
271 :
272 : using Value = muse::number_t<float>;
273 :
274 : Type type = Undefined;
275 : Value val; // [0;1]
276 : layer_idx_t layerIdx = 0;
277 :
278 1 : bool operator==(const ControllerChangeEvent& e) const
279 : {
280 1 : return type == e.type && val == e.val && layerIdx == e.layerIdx;
281 : }
282 : };
283 :
284 : using ControllerChangeEventList = std::vector<ControllerChangeEvent>;
285 :
286 : struct TextArticulationEvent {
287 : enum FlagType : unsigned char {
288 : NoFlags = 0,
289 : StartsAtPlaybackPosition,
290 : };
291 :
292 : String text;
293 : layer_idx_t layerIdx = 0;
294 : Flags<FlagType> flags;
295 :
296 39 : bool operator==(const TextArticulationEvent& e) const
297 : {
298 39 : return text == e.text && layerIdx == e.layerIdx && flags == e.flags;
299 : }
300 : };
301 :
302 : using TextArticulationEventList = std::vector<TextArticulationEvent>;
303 :
304 : struct SoundPresetChangeEvent {
305 : String code;
306 : layer_idx_t layerIdx = 0;
307 :
308 1 : bool operator==(const SoundPresetChangeEvent& e) const
309 : {
310 1 : return code == e.code && layerIdx == e.layerIdx;
311 : }
312 : };
313 :
314 : using SoundPresetChangeEventList = std::vector<SoundPresetChangeEvent>;
315 :
316 : struct SyllableEvent {
317 : enum FlagType : unsigned char {
318 : NoFlags = 0,
319 : StartsAtPlaybackPosition,
320 : HyphenedToNext,
321 : };
322 :
323 : String text;
324 : layer_idx_t layerIdx = 0;
325 : Flags<FlagType> flags;
326 :
327 1 : bool operator==(const SyllableEvent& e) const
328 : {
329 1 : return text == e.text && layerIdx == e.layerIdx && flags == e.flags;
330 : }
331 : };
332 :
333 : using SyllableEventList = std::vector<SyllableEvent>;
334 :
335 : using PlaybackEvent = std::variant<std::monostate,
336 : NoteEvent,
337 : TextArticulationEvent,
338 : SoundPresetChangeEvent,
339 : SyllableEvent,
340 : ControllerChangeEvent>;
341 :
342 : using PlaybackEventList = std::vector<PlaybackEvent>;
343 : using PlaybackEventsMap = SharedMap<timestamp_t, PlaybackEventList>;
344 :
345 : using DynamicAutomationMap = AutomationCurve<timestamp_t>;
346 : using DynamicAutomationLayers = SharedMap<layer_idx_t, DynamicAutomationMap>;
347 :
348 : using MainStreamChanges = async::Channel<PlaybackEventsMap, DynamicAutomationLayers>;
349 : using OffStreamChanges = async::Channel<PlaybackEventsMap, bool /*flushOffstream*/>;
350 :
351 : struct PlaybackData {
352 : PlaybackEventsMap originEvents;
353 : PlaybackSetupData setupData;
354 : DynamicAutomationLayers dynamics;
355 :
356 : MainStreamChanges mainStream;
357 : OffStreamChanges offStream;
358 :
359 1 : bool operator==(const PlaybackData& other) const
360 : {
361 1 : return originEvents == other.originEvents
362 1 : && setupData == other.setupData
363 2 : && dynamics == other.dynamics;
364 : }
365 :
366 0 : bool isValid() const
367 : {
368 0 : return setupData.isValid();
369 : }
370 : };
371 : }
|