1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
use super::{BeatType, Beats, Interchangeable, SenzaMisura};
use crate::datatypes::{
Color, FontFamily, FontSize, FontStyle, FontWeight, Id, LeftCenterRight, StaffNumber, Tenths, TimeSeparator,
TimeSymbol, Valign, YesNo,
};
use alloc::{string::String, vec::Vec};
use musicxml_internal::*;
use musicxml_macros::*;
/// Attributes pertaining to the [Time] element.
#[derive(Debug, Default, PartialEq, Eq, AttributeDeserialize, AttributeSerialize)]
pub struct TimeAttributes {
/// Indicates the color of an element.
pub color: Option<Color>,
/// Changes the computation of the default horizontal position.
/// The origin is changed relative to the left-hand side of the note or the musical position within the bar.
/// Positive x is right and negative x is left.
///
/// This attribute provides higher-resolution positioning data than the [Offset][super::Offset] element.
/// Applications reading a MusicXML file that can understand both features should generally rely on this attribute for its greater accuracy.
pub default_x: Option<Tenths>,
/// Changes the computation of the default vertical position.
/// The origin is changed relative to the top line of the staff. Positive y is up and negative y is down.
///
/// This attribute provides higher-resolution positioning data than the `placement` attribute.
/// Applications reading a MusicXML file that can understand both attributes should generally rely on this attribute for its greater accuracy.
pub default_y: Option<Tenths>,
/// A comma-separated list of font names.
pub font_family: Option<FontFamily>,
/// One of the CSS sizes or a numeric point size.
pub font_size: Option<FontSize>,
/// Normal or italic style.
pub font_style: Option<FontStyle>,
/// Normal or bold weight.
pub font_weight: Option<FontWeight>,
/// In cases where text extends over more than one line, horizontal alignment and justify values can be different.
/// The most typical case is for credits, such as:
///
/// ```text
/// Words and music by
/// Pat Songwriter
/// ```
/// Typically this type of credit is aligned to the right, so that the position information refers to the right-most part of the text.
/// But in this example, the text is center-justified, not right-justified.
///
/// The `halign` attribute is used in these situations. If it is not present, its value is the same as for the `justify` attribute.
/// For elements where a justify attribute is not allowed, the default is implementation-dependent.
pub halign: Option<LeftCenterRight>,
/// Specifies an ID that is unique to the entire document.
pub id: Option<Id>,
/// Allows a time signature to apply to only the specified staff in the part. If absent, the time signature applies to all staves in the part.
pub number: Option<StaffNumber>,
/// Specifies whether or not to print an object. It is yes if not specified.
pub print_object: Option<YesNo>,
/// Changes the horizontal position relative to the default position, either as computed by the individual program, or as overridden by the `default_x` attribute.
/// Positive x is right and negative x is left. It should be interpreted in the context of the [Offset][super::Offset] element or directive attribute if those are present.
pub relative_x: Option<Tenths>,
/// Changes the vertical position relative to the default position, either as computed by the individual program, or as overridden by the `default_y` attribute.
/// Positive y is up and negative y is down. It should be interpreted in the context of the `placement` attribute if that is present.
pub relative_y: Option<Tenths>,
/// Indicates how to display the arrangement between the [Beats] and [BeatType] values in a time signature.
pub separator: Option<TimeSeparator>,
/// Indicates how to display a time signature, such as by using common and cut time symbols or a single number display.
pub symbol: Option<TimeSymbol>,
/// Indicates vertical alignment to the top, middle, bottom, or baseline of the text. The default is implementation-dependent.
pub valign: Option<Valign>,
}
/// The [TimeBeatContents] element specifies the number of beats in a measure and the beat unit.
#[derive(Debug, PartialEq, Eq)]
pub struct TimeBeatContents {
/// The [Beats] element specifies the number of beats in a measure.
pub beats: Beats,
/// The [BeatType] element specifies the beat unit, as found in the denominator of a time signature.
pub beat_type: BeatType,
}
/// Contents of the [Time] element.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct TimeContents {
/// The [TimeBeatContents] element specifies the number of beats in a measure and the beat unit.
pub beats: Vec<TimeBeatContents>,
/// The [Interchangeable] element allows the time signature to be specified but not printed.
pub interchangeable: Option<Interchangeable>,
/// The [SenzaMisura] element specifies that the music is without a time signature.
pub senza_misura: Option<SenzaMisura>,
}
impl ContentDeserializer for TimeContents {
fn deserialize(elements: &[XmlElement]) -> Result<Self, String> {
let mut beats = Vec::new();
let mut time_beats = None;
let mut interchangeable = None;
let mut senza_misura = None;
for element in elements {
match element.name.as_str() {
"beats" => time_beats = Some(Beats::deserialize(element)?),
"beat-type" => {
beats.push(TimeBeatContents {
beats: time_beats.ok_or("Missing required <beats> element in <time>")?,
beat_type: BeatType::deserialize(element)?,
});
time_beats = None;
}
"interchangeable" => interchangeable = Some(Interchangeable::deserialize(element)?),
"senza-misura" => senza_misura = Some(SenzaMisura::deserialize(element)?),
_ => return Err(format!("Invalid element name: {}", element.name)),
}
}
Ok(TimeContents {
beats,
interchangeable,
senza_misura,
})
}
}
impl ContentSerializer for TimeContents {
fn serialize(element: &Self) -> Vec<XmlElement> {
let mut elements: Vec<XmlElement> = Vec::new();
for el in &element.beats {
elements.push(Beats::serialize(&el.beats));
elements.push(BeatType::serialize(&el.beat_type));
}
if let Some(content) = &element.interchangeable {
elements.push(Interchangeable::serialize(content));
}
if let Some(content) = &element.senza_misura {
let mut xml_element = SenzaMisura::serialize(content);
xml_element.name = String::from("senza-misura");
elements.push(xml_element);
}
elements
}
}
/// Time signatures are represented by the [Beats] element for the numerator and the [BeatType] element for the denominator.
///
/// 
///
/// Multiple pairs of [Beats] and [BeatType] elements are used for composite time signatures with multiple denominators, such as 2/4 + 3/8.
/// A composite such as 3+2/8 requires only one [Beats]/[BeatType] pair.
///
/// The `print_object` attribute allows a time signature to be specified but not printed, as is the case for excerpts from the middle of a score.
/// The value is "yes" if not present.
#[derive(Debug, PartialEq, Eq, ElementDeserialize, ElementSerialize)]
pub struct Time {
/// Element-specific attributes
pub attributes: TimeAttributes,
#[flatten]
/// Element-specific content
pub content: TimeContents,
}
#[cfg(test)]
mod time_tests {
use crate::elements::*;
use crate::{
elements::InterchangeableContents,
parser::{parse_from_xml_str, parse_to_xml_str},
};
use alloc::{string::String, vec::Vec};
#[test]
fn serialize_valid1() {
let test = Time {
attributes: TimeAttributes::default(),
content: TimeContents {
beats: vec![
TimeBeatContents {
beats: Beats {
attributes: (),
content: String::from("3"),
},
beat_type: BeatType {
attributes: (),
content: String::from("4"),
},
},
TimeBeatContents {
beats: Beats {
attributes: (),
content: String::from("6"),
},
beat_type: BeatType {
attributes: (),
content: String::from("8"),
},
},
],
interchangeable: Some(Interchangeable {
attributes: InterchangeableAttributes::default(),
content: InterchangeableContents {
time_relation: Some(TimeRelation {
attributes: (),
content: crate::datatypes::TimeRelation::Bracket,
}),
beat_data: vec![InterchangeableBeatData {
beats: Beats {
attributes: (),
content: String::from("4"),
},
beat_type: BeatType {
attributes: (),
content: String::from("4"),
},
}],
},
}),
senza_misura: None,
},
};
let expected = "<time>
<beats>3</beats>
<beat-type>4</beat-type>
<beats>6</beats>
<beat-type>8</beat-type>
<interchangeable>
<time-relation>bracket</time-relation>
<beats>4</beats>
<beat-type>4</beat-type>
</interchangeable>
</time>";
let result = parse_to_xml_str(&test, true);
assert_eq!(result, expected);
}
#[test]
fn serialize_valid2() {
let test = Time {
attributes: TimeAttributes::default(),
content: TimeContents {
beats: Vec::new(),
interchangeable: None,
senza_misura: Some(SenzaMisura {
attributes: (),
content: String::from("Senza Test"),
}),
},
};
let expected = "<time><senza-misura>Senza Test</senza-misura></time>";
let result = parse_to_xml_str(&test, false);
assert_eq!(result, expected);
}
#[test]
fn deserialize_valid1() {
let result = parse_from_xml_str::<Time>(
"<time>
<beats>3</beats>
<beat-type>4</beat-type>
<beats>6</beats>
<beat-type>8</beat-type>
<interchangeable>
<time-relation>bracket</time-relation>
<beats>4</beats>
<beat-type>4</beat-type>
</interchangeable>
</time>",
);
assert!(result.is_ok());
assert_eq!(
result.unwrap(),
Time {
attributes: TimeAttributes::default(),
content: TimeContents {
beats: vec![
TimeBeatContents {
beats: Beats {
attributes: (),
content: String::from("3")
},
beat_type: BeatType {
attributes: (),
content: String::from("4")
},
},
TimeBeatContents {
beats: Beats {
attributes: (),
content: String::from("6")
},
beat_type: BeatType {
attributes: (),
content: String::from("8")
},
},
],
interchangeable: Some(Interchangeable {
attributes: InterchangeableAttributes::default(),
content: InterchangeableContents {
time_relation: Some(TimeRelation {
attributes: (),
content: crate::datatypes::TimeRelation::Bracket,
}),
beat_data: vec![InterchangeableBeatData {
beats: Beats {
attributes: (),
content: String::from("4")
},
beat_type: BeatType {
attributes: (),
content: String::from("4")
},
}],
},
}),
senza_misura: None,
}
}
);
}
}