Skip to main content

music21/common/
enums.rs

1//! The members of music21's `common.enums`, and the hexadecimal repr its
2//! `HexEnum` gives.
3//!
4//! The enums themselves are Python classes; what is here is their members,
5//! by music21's names and values, so that anything building them builds
6//! music21's.
7
8/// A member's value: music21's enums hold text, except `GatherSpanners`,
9/// two of whose members are `True` and `False`.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum MemberValue {
12    /// A boolean value.
13    Bool(bool),
14    /// A text value.
15    Text(&'static str),
16}
17
18/// One member: its name and its value.
19pub type Member = (&'static str, MemberValue);
20
21use MemberValue::{Bool, Text};
22
23/// `ElementSearch`: the directions `getContextByClass` can search in, each
24/// the name of the `Stream` method it searches with.
25pub const ELEMENT_SEARCH: &[Member] = &[
26    ("BEFORE", Text("getElementBefore")),
27    ("AFTER", Text("getElementAfter")),
28    ("AT_OR_BEFORE", Text("getElementAtOrBefore")),
29    ("AT_OR_AFTER", Text("getElementAtOrAfter")),
30    ("BEFORE_OFFSET", Text("getElementBeforeOffset")),
31    ("AFTER_OFFSET", Text("getElementAfterOffset")),
32    ("AT_OR_BEFORE_OFFSET", Text("getElementAtOrBeforeOffset")),
33    ("AT_OR_AFTER_OFFSET", Text("getElementAtOrAfterOffset")),
34    ("BEFORE_NOT_SELF", Text("getElementBeforeNotSelf")),
35    ("AFTER_NOT_SELF", Text("getElementAfterNotSelf")),
36    ("ALL", Text("all")),
37];
38
39/// `OffsetSpecial`: offsets that are not numbers.
40pub const OFFSET_SPECIAL: &[Member] = &[
41    ("AT_END", Text("highestTime")),
42    ("LOWEST_OFFSET", Text("lowestOffset")),
43    ("HIGHEST_OFFSET", Text("highestOffset")),
44];
45
46/// `GatherSpanners`: which spanners an excerpt gathers.
47pub const GATHER_SPANNERS: &[Member] = &[
48    ("ALL", Bool(true)),
49    ("NONE", Bool(false)),
50    ("COMPLETE_ONLY", Text("completeOnly")),
51];
52
53/// `AppendSpanners`: whether the spanners an object starts or ends go out
54/// with it when it is written.
55pub const APPEND_SPANNERS: &[Member] = &[
56    ("NORMAL", Text("normal")),
57    ("RELATED_ONLY", Text("related_only")),
58    ("NONE", Text("none")),
59];
60
61/// `OrnamentDelay`: the delays an ornament can have that are not a length.
62pub const ORNAMENT_DELAY: &[Member] = &[
63    ("NO_DELAY", Text("noDelay")),
64    ("DEFAULT_DELAY", Text("defaultDelay")),
65];
66
67/// `MeterDivision`: how a time signature is divided.
68pub const METER_DIVISION: &[Member] = &[
69    ("FAST", Text("fast")),
70    ("SLOW", Text("slow")),
71    ("NONE", Text("none")),
72];
73
74/// The text with every run of decimal digits written in upper-case
75/// hexadecimal after `0x`, which is how `HexEnum` turns an `IntEnum`'s
76/// repr into its own. Runs of any length are converted.
77///
78/// ```
79/// use music21::common::enums::hex_numbers;
80///
81/// assert_eq!(
82///     hex_numbers("<ChannelVoiceMessages.NOTE_ON: 144>"),
83///     "<ChannelVoiceMessages.NOTE_ON: 0x90>"
84/// );
85/// ```
86pub fn hex_numbers(text: &str) -> String {
87    let mut out = String::with_capacity(text.len());
88    let mut digits = String::new();
89    for character in text.chars() {
90        if character.is_ascii_digit() {
91            digits.push(character);
92            continue;
93        }
94        if !digits.is_empty() {
95            out.push_str("0x");
96            out.push_str(&decimal_to_hex(&digits));
97            digits.clear();
98        }
99        out.push(character);
100    }
101    if !digits.is_empty() {
102        out.push_str("0x");
103        out.push_str(&decimal_to_hex(&digits));
104    }
105    out
106}
107
108/// A run of decimal digits in hexadecimal, however long the run.
109fn decimal_to_hex(digits: &str) -> String {
110    let mut number: Vec<u8> = digits.bytes().map(|digit| digit - b'0').collect();
111    let mut hex = Vec::new();
112    while number.iter().any(|&digit| digit != 0) {
113        let mut remainder = 0u32;
114        for digit in &mut number {
115            let value = remainder * 10 + u32::from(*digit);
116            *digit = u8::try_from(value / 16).unwrap_or(0);
117            remainder = value % 16;
118        }
119        hex.push(
120            char::from_digit(remainder, 16)
121                .unwrap_or('0')
122                .to_ascii_uppercase(),
123        );
124    }
125    if hex.is_empty() {
126        return "0".to_string();
127    }
128    hex.iter().rev().collect()
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    /// Every run is converted, a class name's digits too, and a run too long
136    /// for any integer type is converted all the same.
137    #[test]
138    fn every_digit_run_is_written_in_hex() {
139        assert_eq!(hex_numbers("<A2.B: 0>"), "<A0x2.B: 0x0>");
140        assert_eq!(hex_numbers("255"), "0xFF");
141        assert_eq!(hex_numbers("007"), "0x7");
142        assert_eq!(
143            hex_numbers("340282366920938463463374607431768211456"),
144            "0x100000000000000000000000000000000"
145        );
146        assert_eq!(hex_numbers("no digits ß"), "no digits ß");
147    }
148
149    /// Names are unique within each enum.
150    #[test]
151    fn member_names_are_unique() {
152        for table in [
153            ELEMENT_SEARCH,
154            OFFSET_SPECIAL,
155            GATHER_SPANNERS,
156            APPEND_SPANNERS,
157            ORNAMENT_DELAY,
158            METER_DIVISION,
159        ] {
160            let mut names: Vec<_> = table.iter().map(|(name, _)| name).collect();
161            names.sort();
162            names.dedup();
163            assert_eq!(names.len(), table.len());
164        }
165    }
166}