MNX Document Model
Loading...
Searching...
No Matches
WalkSequenceContent.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include <functional>
25
26#include "../Part.h"
27
28namespace mnx::util {
29
37
39enum class SequenceWalkControl
40{
41 Continue,
42 SkipChildren,
43 Stop
44};
45
48{
50 std::function<SequenceWalkControl(const ContentObject& item,
52
54 std::function<bool(const sequence::Event& event,
55 const FractionValue& startDuration,
56 const FractionValue& actualDuration,
58
60 std::function<bool(const Sequence& sequence,
61 const sequence::FullMeasureRest& fullMeasure,
62 const FractionValue& startDuration,
63 const FractionValue& actualDuration,
65
67 std::function<void(const ContentObject& item,
69};
70
76inline bool walkSequenceContent(Sequence sequence,
77 const SequenceWalkHooks& hooks,
78 SequenceWalkContext* ctx = nullptr)
79{
80 SequenceWalkContext localCtx;
81 SequenceWalkContext& c = ctx ? *ctx : localCtx;
82
83 if (const auto fullMeasure = sequence.fullMeasure()) {
84 FractionValue fullMeasureDuration { 0 };
85 if (const auto measure = sequence.getEnclosingElement<part::Measure>()) {
86 if (const auto time = measure->calcCurrentTime()) {
87 fullMeasureDuration = static_cast<FractionValue>(*time);
88 }
89 }
90 const auto start = c.elapsedTime;
91 if (hooks.onFullMeasure) {
92 if (!hooks.onFullMeasure(sequence, fullMeasure.value(), start, fullMeasureDuration, c)) {
93 return false;
94 }
95 }
96 c.elapsedTime += fullMeasureDuration;
97 }
98
99 auto walkImpl = [&](ContentArray current,
100 SequenceWalkContext& ctxRef,
101 auto&& self) -> bool
102 {
103 for (const auto item : current) {
104 bool allowChildren = true;
105
106 if (hooks.onItem) {
107 const SequenceWalkControl control = hooks.onItem(item, ctxRef);
108 if (control == SequenceWalkControl::Stop) {
109 return false;
110 }
111 if (control == SequenceWalkControl::SkipChildren) {
112 allowChildren = false;
113 }
114 }
115
116 if (item.type() == sequence::Event::ContentTypeValue) {
117 const auto event = item.get<sequence::Event>();
118 const auto start = ctxRef.elapsedTime;
119 const auto actualDuration = event.duration() * ctxRef.timeRatio;
120 if (hooks.onEvent) {
121 if (!hooks.onEvent(event, start, actualDuration, ctxRef)) {
122 return false;
123 }
124 }
125 ctxRef.elapsedTime += actualDuration;
126 } else if (item.type() == sequence::Grace::ContentTypeValue) {
127 if (allowChildren) {
128 const auto grace = item.get<sequence::Grace>();
129 SequenceWalkContext child = ctxRef;
130 child.timeRatio = 0;
131 child.inGrace = true;
132 if (!self(grace.content(), child, self)) {
133 return false;
134 }
135 // Grace is time-neutral: elapsedTime does not change.
136 }
137 } else if (item.type() == sequence::Tuplet::ContentTypeValue) {
138 const auto tuplet = item.get<sequence::Tuplet>();
139 if (allowChildren) {
140 SequenceWalkContext child = ctxRef;
141 child.timeRatio = ctxRef.timeRatio * tuplet.ratio();
142 if (!self(tuplet.content(), child, self)) {
143 return false;
144 }
145 // Propagate elapsedTime back to the parent context.
146 ctxRef.elapsedTime = child.elapsedTime;
147 } else {
148 // Children skipped: assume well-formed tuplet content.
149 ctxRef.elapsedTime += tuplet.outer() * ctxRef.timeRatio;
150 }
151 } else if (item.type() == sequence::MultiNoteTremolo::ContentTypeValue) {
152 const auto tremolo = item.get<sequence::MultiNoteTremolo>();
153 const auto multiple = tremolo.outer().multiple();
154 if (allowChildren && multiple > 0) {
155 const auto startTime = ctxRef.elapsedTime;
156 const auto expectedDuration = tremolo.outer() * ctxRef.timeRatio;
157 SequenceWalkContext child = ctxRef;
158 child.timeRatio = mnx::FractionValue(1, multiple) * ctxRef.timeRatio;
159 if (!self(tremolo.content(), child, self)) {
160 return false;
161 }
162 const auto actualDuration = child.elapsedTime - startTime;
163 if (actualDuration != expectedDuration) {
164 // Silently recover for now. Ultimately, we will have to revisit this if (when) MNX revises tremolos.
165 child.elapsedTime = startTime + expectedDuration;
166 }
167 ctxRef.elapsedTime = child.elapsedTime;
168 } else {
169 ctxRef.elapsedTime += tremolo.outer() * ctxRef.timeRatio;
170 }
171 } else if (item.type() == sequence::Space::ContentTypeValue) {
172 const auto space = item.get<sequence::Space>();
173 ctxRef.elapsedTime += space.duration() * ctxRef.timeRatio;
174 }
175
176 if (hooks.onAfterItem) {
177 hooks.onAfterItem(item, ctxRef);
178 }
179 }
180
181 return true;
182 };
183
184 return walkImpl(sequence.content(), c, walkImpl);
185}
186
202inline bool iterateSequenceEvents(Sequence sequence,
203 std::function<bool(const sequence::Event& event,
204 const FractionValue& startDuration,
205 const FractionValue& actualDuration)> iterator)
206{
207 SequenceWalkHooks hooks;
208 hooks.onEvent = [&](const sequence::Event& event,
209 const FractionValue& startDuration,
210 const FractionValue& actualDuration,
211 SequenceWalkContext&) -> bool
212 {
213 // Preserve the public callback signature: pass Event by value.
214 return iterator(event, startDuration, actualDuration);
215 };
216
217 return walkSequenceContent(sequence, hooks);
218}
219
220} // namespace mnx::util
std::optional< T > getEnclosingElement() const
Returns the enclosing array element for this instance. If T is a type that can be nested (e....
Definition Implementations.cpp:69
Class for content arrays.
Definition BaseTypes.h:763
Base class for objects that are elements of content arrays.
Definition BaseTypes.h:685
A sequence of events and other items in this measure for a voice in a part.
Definition Sequence.h:695
Represents a single measure in a part in an MNX document. It contains the majority of the musical inf...
Definition Part.h:356
Represents a musical event within a sequence.
Definition Sequence.h:418
static constexpr std::string_view ContentTypeValue
type value that identifies the type within the content array
Definition Sequence.h:483
Represents a page in a score.
Definition Sequence.h:673
static constexpr std::string_view ContentTypeValue
type value that identifies the type within the content array
Definition Sequence.h:556
static constexpr std::string_view ContentTypeValue
type value that identifies the type within the content array
Definition Sequence.h:607
static constexpr std::string_view ContentTypeValue
type value that identifies the type within the content array
Definition Sequence.h:526
static constexpr std::string_view ContentTypeValue
type value that identifies the type within the content array
Definition Sequence.h:665
Represents a detached arithmetic fraction with normalization.
Definition CommonClasses.h:59
Traversal context passed through walkSequenceContent.
Definition WalkSequenceContent.h:32
bool inGrace
true while descending through grace note content
Definition WalkSequenceContent.h:35
FractionValue timeRatio
accumulated tuplet time ratio.
Definition WalkSequenceContent.h:34
FractionValue elapsedTime
current elapsed time.
Definition WalkSequenceContent.h:33
Hook set for walkSequenceContent.
Definition WalkSequenceContent.h:48
std::function< bool(const sequence::Event &event, const FractionValue &startDuration, const FractionValue &actualDuration, SequenceWalkContext &ctx)> onEvent
Called for events with computed timing.
Definition WalkSequenceContent.h:57
std::function< void(const ContentObject &item, SequenceWalkContext &ctx)> onAfterItem
Called for every content object after recursion / time advancement.
Definition WalkSequenceContent.h:68
std::function< SequenceWalkControl(const ContentObject &item, SequenceWalkContext &ctx)> onItem
Called for every content object before recursion or time advancement.
Definition WalkSequenceContent.h:51
std::function< bool(const Sequence &sequence, const sequence::FullMeasureRest &fullMeasure, const FractionValue &startDuration, const FractionValue &actualDuration, SequenceWalkContext &ctx)> onFullMeasure
Called when the sequence is a full-measure rest.
Definition WalkSequenceContent.h:64