MUSX Document Model
Loading...
Searching...
No Matches
FieldPopulatorsOthers.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 "musx/dom/BaseClasses.h"
25#include "musx/dom/Others.h"
26#include "musx/dom/Details.h"
27#include "musx/xml/XmlInterface.h"
28#include "FactoryBase.h"
29
30#ifndef DOXYGEN_SHOULD_IGNORE_THIS
31
32namespace musx {
33namespace factory {
34
35using namespace ::musx::xml;
36using namespace ::musx::dom::others;
37
38// Field populators are maintained to populate in the order that nodes are observed to occur in EnigmaXml.
39// The goal is that this may facilitate serialization in the future.
40
41template <>
42inline Enclosure::Shape toEnum<Enclosure::Shape>(const uint8_t& value)
43{
44 if (value >= static_cast<uint8_t>(Enclosure::Shape::NoEnclosure) &&
45 value <= static_cast<uint8_t>(Enclosure::Shape::Octogon)) {
46 return static_cast<Enclosure::Shape>(value);
47 }
48 MUSX_UNKNOWN_XML("Invalid <sides> value in XML for enclosure: " + std::to_string(value));
49 return {};
50}
51
52template <>
53struct FieldPopulator<DrumStaffStyle> : private FieldPopulator<DrumStaff>
54{
55 using FieldPopulator<DrumStaff>::populate;
56};
57
58template <>
59struct FieldPopulator<NamePositionAbbreviated> : private FieldPopulator<NamePositioning>
60{
61 using FieldPopulator<NamePositioning>::populate;
62};
63
64template <>
65struct FieldPopulator<NamePositionStyleAbbreviated> : private FieldPopulator<NamePositioning>
66{
67 using FieldPopulator<NamePositioning>::populate;
68};
69
70template <>
71struct FieldPopulator<NamePositionFull> : private FieldPopulator<NamePositioning>
72{
73 using FieldPopulator<NamePositioning>::populate;
74};
75
76template <>
77struct FieldPopulator<NamePositionStyleFull> : private FieldPopulator<NamePositioning>
78{
79 using FieldPopulator<NamePositioning>::populate;
80};
81
82template <>
83struct FieldPopulator<TextExpressionEnclosure> : private FieldPopulator<Enclosure>
84{
85 using FieldPopulator<Enclosure>::populate;
86};
87
88template <>
89struct FieldPopulator<TextRepeatEnclosure> : private FieldPopulator<Enclosure>
90{
91 using FieldPopulator<Enclosure>::populate;
92};
93
94MUSX_RESOLVER_ENTRY(KeyMapArray, {
95 [](const dom::DocumentPtr& document) {
96 auto arrays = document->getOthers()->getArray<KeyMapArray>(SCORE_PARTID);
97 for (const auto& array : arrays) {
98 auto trimSteps = [&](size_t newSize) {
99 while (array->steps.size() > newSize) {
100 const auto& elt = array->steps[array->steps.size() - 1];
101 if (elt->diatonic || elt->hlevel != 0) {
102 break; // itegrity check below will catch this error
103 }
104 array->steps.pop_back();
105 }
106 };
107 if (auto keyFormat = document->getOthers()->get<others::KeyFormat>(SCORE_PARTID, array->getCmper())) {
108 trimSteps(keyFormat->semitones);
109 if (keyFormat->scaleTones != array->countDiatonicSteps() || keyFormat->semitones != array->steps.size()) {
110 MUSX_INTEGRITY_ERROR("KeyMapArray " + std::to_string(array->getCmper()) + " does not match KeyFormat.");
111 }
112 } else {
113 trimSteps(12);
114 if (array->countDiatonicSteps() != 7 || array->steps.size() != 12) { // default diatonic
115 MUSX_INTEGRITY_ERROR("KeyMapArray " + std::to_string(array->getCmper()) + " has no KeyFormat but does not match default values.");
116 }
117 }
118 }
119 }
120});
121
122MUSX_RESOLVER_ENTRY(LayerAttributes, {
123 [](const dom::DocumentPtr& document) {
124 auto layers = document->getOthers()->getArray<LayerAttributes>(SCORE_PARTID);
125 if (layers.size() != 4) {
126 MUSX_INTEGRITY_ERROR("Expected exactly 4 <layerAtts> elements.");
127 }
128 for (size_t i = 0; i < layers.size(); i++) {
129 if (layers[i]->getCmper() != i) {
130 MUSX_INTEGRITY_ERROR("Expected <layerAtts> elements to have cmper values 0, 1, 2, 3 in order.");
131 }
132 }
133 }
134});
135
136MUSX_RESOLVER_ENTRY(MarkingCategory, {
137 [](const dom::DocumentPtr& document) {
138 auto cats = document->getOthers()->getArray<MarkingCategory>(SCORE_PARTID);
139 for (const auto& cat : cats) {
140 if (cat->categoryType == MarkingCategory::CategoryType::Invalid) {
141 MUSX_INTEGRITY_ERROR("Encountered <markingsCategory> node (cmper " + std::to_string(cat->getCmper()) + ") with no categoryType");
142 }
143 }
144 }
145});
146
147MUSX_RESOLVER_ENTRY(MultiStaffGroupId, {
148 [](const dom::DocumentPtr& document) {
149 auto parts = document->getOthers()->getArray<PartDefinition>(SCORE_PARTID);
150 for (const auto& part : parts) {
151 auto instGroups = document->getOthers()->getArray<MultiStaffGroupId>(part->getCmper());
152 for (const auto& instance : instGroups) {
153 if (auto group = document->getDetails()->get<details::StaffGroup>(part->getCmper(), BASE_SYSTEM_ID, instance->staffGroupId)) {
154 group->multiStaffGroupId = instance->getCmper();
155 } else {
156 MUSX_INTEGRITY_ERROR("Group " + std::to_string(instance->staffGroupId) + " appears in MultiStaffGroupId "
157 + std::to_string(instance->getCmper()) + " but does not exist.");
158 }
159 }
160 }
161 }
162});
163
164MUSX_RESOLVER_ENTRY(MultiStaffInstrumentGroup, {
165 [](const dom::DocumentPtr& document) {
166 auto instGroups = document->getOthers()->getArray<MultiStaffInstrumentGroup>(SCORE_PARTID);
167 for (const auto& instance : instGroups) {
168 for (size_t x = 0; x < instance->staffNums.size(); x++) {
169 auto staff = instance->getStaffAtIndex(x);
170 if (staff) {
171 if (staff->multiStaffInstId) {
173 "Staff " + std::to_string(staff->getCmper()) + " appears in more than one instance of MultiStaffInstrumentGroup.");
174 } else {
175 staff->multiStaffInstId = instance->getCmper();
176 }
177 }
178 }
179 }
181 }
182});
183
184MUSX_RESOLVER_ENTRY(Page, {
185 [](const dom::DocumentPtr& document) {
186 auto linkedParts = document->getOthers()->getArray<PartDefinition>(SCORE_PARTID);
187 for (const auto& part : linkedParts) {
188 auto pages = document->getOthers()->getArray<Page>(part->getCmper());
189 auto systems = document->getOthers()->getArray<StaffSystem>(part->getCmper());
190 for (size_t x = 0; x < pages.size(); x++) {
191 auto page = pages[x];
192 if (!page->isBlank()) {
193 page->lastSystem = [&]() -> SystemCmper {
194 size_t nextIndex = x + 1;
195 while (nextIndex < pages.size()) {
196 auto nextPage = pages[nextIndex++];
197 if (!nextPage->isBlank()) {
198 return nextPage->firstSystem - 1;
199 }
200 }
201 return SystemCmper(systems.size());
202 }();
203 if (*page->lastSystem < page->firstSystem) {
204 page->lastSystem = std::nullopt;
205 MUSX_INTEGRITY_ERROR("Page " + std::to_string(page->getCmper()) + " of part " + part->getName()
206 + " has a last system smaller than the first system.");
207 }
208 }
209 }
210 }
211 }
212});
213
214MUSX_RESOLVER_ENTRY(ShapeExpressionDef, {
215 [](const dom::DocumentPtr& document) {
216 auto exps = document->getOthers()->getArray<ShapeExpressionDef>(SCORE_PARTID);
217 for (const auto& instance : exps) {
218 if (instance->categoryId) {
219 auto markingCat = document->getOthers()->get<MarkingCategory>(instance->getPartId(), instance->categoryId);
220 if (!markingCat) {
221 MUSX_INTEGRITY_ERROR("Marking category for shape expression " + std::to_string(instance->getCmper()) + " does not exist.");
222 }
223 markingCat->shapeExpressions.emplace(instance->getCmper(), instance);
224 }
225 }
226 }
227});
228
229MUSX_RESOLVER_ENTRY(Staff, {
230 [](const dom::DocumentPtr& document) {
231 others::Staff::calcAllRuntimeValues<others::Staff>(document);
232 auto instGroups = document->getOthers()->getArray<MultiStaffInstrumentGroup>(SCORE_PARTID);
233 // If no MultiStaffInstrumentGroup records exist, then we need to do this here.
234 if (instGroups.empty()) {
236 }
237 }
238});
239
240MUSX_RESOLVER_ENTRY(StaffStyle, {
241 [](const dom::DocumentPtr& document) {
242 others::Staff::calcAllRuntimeValues<others::StaffStyle>(document);
243 }
244});
245
246MUSX_RESOLVER_ENTRY(TextExpressionDef, {
247 [](const dom::DocumentPtr& document) {
248 auto exps = document->getOthers()->getArray<TextExpressionDef>(SCORE_PARTID);
249 for (const auto& instance : exps) {
250 if (instance->categoryId) {
251 auto markingCat = document->getOthers()->get<MarkingCategory>(instance->getPartId(), instance->categoryId);
252 if (!markingCat) {
253 MUSX_INTEGRITY_ERROR("Marking category for text expression " + std::to_string(instance->getCmper()) + " does not exist.");
254 }
255 markingCat->textExpressions.emplace(instance->getCmper(), instance);
256 }
257 }
258 }
259});
260
261} // namespace factory
262} // namespace musx
263
264#endif // DOXYGEN_SHOULD_IGNORE THIS
static void calcAllAutoNumberValues(const DocumentPtr &document)
Get the auto-numbering value for this staff, if applicable.
Definition Implementations.cpp:2452
@ Verbose
Informational messages that should only displayed when verbose logging is requested.
static void log(LogLevel level, const std::string &message)
Logs a message with a specific severity level.
Definition Logger.h:87
Classes in the OthersPool.
Definition CommonClasses.h:332
constexpr Cmper SCORE_PARTID
The part id of the score.
Definition Fundamentals.h:80
std::shared_ptr< Document > DocumentPtr
Shared Document pointer.
Definition BaseClasses.h:51
constexpr Cmper BASE_SYSTEM_ID
The base system cmper that gives a list of all available staves and their score order (others::Instru...
Definition Fundamentals.h:81
int16_t SystemCmper
Enigma systems Cmper (may be negative when not applicable)
Definition Fundamentals.h:66
Provides interfaces and optional implementations for traversing XML.
Definition PugiXmlImpl.h:33
object model for musx file (enigmaxml)
Definition BaseClasses.h:32