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
use alloc::string::{String, ToString};
use core::ops::Deref;
use musicxml_internal::{DatatypeDeserializer, DatatypeSerializer};
use musicxml_macros::DatatypeSerialize;
/// Distinguishes up to 16 concurrent objects of the same type when the objects overlap in MusicXML document order.
///
/// Values greater than 6 are usually only needed for music with a large number of divisi staves in a single part,
/// or if there are more than 6 cross-staff arpeggios in a single measure. When a number-level value is optional and
/// has no default value, it is 1 if not specified.
///
/// When polyphonic parts are involved, the ordering within a MusicXML document can differ from musical score order.
/// As an example, say we have a piano part in 4/4 where within a single measure, all the notes on the top staff are
/// followed by all the notes on the bottom staff. In this example, each staff has a slur that starts on beat 2 and
/// stops on beat 3, and there is a third slur that goes from beat 1 of one staff to beat 4 of the other staff.
///
/// In this situation, the two mid-measure slurs can use the same number because they do not overlap in MusicXML document
/// order, even though they do overlap in musical score order. Within the MusicXML document, the top staff slur will
/// both start and stop before the bottom staff slur starts and stops.
///
/// If the cross-staff slur starts in the top staff and stops in the bottom staff, it will need a separate number from
/// the mid-measure slurs because it overlaps those slurs in MusicXML document order. However, if the cross-staff slur
/// starts in the bottom staff and stops in the top staff, all three slurs can use the same number. None of them overlap
/// within the MusicXML document, even though they all overlap each other in the musical score order. Within the MusicXML
/// document, the start and stop of the top-staff slur will be followed by the stop and start of the cross-staff slur,
/// followed by the start and stop of the bottom-staff slur.
///
/// As this example demonstrates, a reading program should be prepared to handle cases where the number-levels start
/// and stop in an arbitrary order. Because the start and stop values refer to musical score order, a program may
/// find the stopping point of an object earlier in the MusicXML document than it will find its starting point.
///
/// The value of an instance of this type may be accessed by dereferencing the struct: `*datatype_val`.
///
/// **Minimum value**: 1
///
/// **Maximum value**: 16
#[derive(Debug, PartialEq, Eq, DatatypeSerialize)]
pub struct NumberLevel(pub u8);
impl Deref for NumberLevel {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DatatypeDeserializer for NumberLevel {
fn deserialize(value: &str) -> Result<Self, String> {
match value.parse::<u8>() {
Ok(val) => match val {
1..=16 => Ok(NumberLevel(val)),
_ => Err(format!("Value {val} is invalid for the <number-level> data type")),
},
Err(err) => {
if err.to_string().to_lowercase().contains("eof") {
Ok(NumberLevel(1))
} else {
Err(format!("Value {value} is invalid for the <number-level> data type"))
}
}
}
}
}
#[cfg(test)]
mod number_level_tests {
use super::*;
#[test]
fn deserialize_valid1() {
let result = NumberLevel::deserialize("1");
assert!(result.is_ok());
assert_eq!(result.unwrap(), NumberLevel(1));
}
#[test]
fn deserialize_valid2() {
let result = NumberLevel::deserialize("16");
assert!(result.is_ok());
assert_eq!(result.unwrap(), NumberLevel(16));
}
#[test]
fn deserialize_invalid1() {
let result = NumberLevel::deserialize("0");
assert!(result.is_err());
}
#[test]
fn deserialize_invalid2() {
let result = NumberLevel::deserialize("17");
assert!(result.is_err());
}
#[test]
fn deserialize_invalid3() {
let result = NumberLevel::deserialize("-2");
assert!(result.is_err());
}
#[test]
fn deserialize_invalid4() {
let result = NumberLevel::deserialize("");
assert!(result.is_err());
}
}