Skip to main content

music21_rs/pitch/
display.rs

1//! Whether a written accidental is needed: music21's
2//! `updateAccidentalDisplay`, with its rules for repeats, octaves, ties,
3//! simultaneities and key signatures.
4
5use super::*;
6
7impl Pitch {
8    /// music21's `_nameInKeySignature`: whether one of a key signature's
9    /// altered pitches has this pitch's step and the same accidental. A
10    /// pitch with no accidental object never matches.
11    pub fn name_in_key_signature(&self, altered_pitches: &[Pitch]) -> bool {
12        let Some(name) = self.accidental_name() else {
13            return false;
14        };
15        altered_pitches
16            .iter()
17            .any(|p| p.step == self.step && p.accidental_name() == Some(name))
18    }
19
20    /// music21's `_stepInKeySignature`: whether a key signature alters this
21    /// pitch's step at all, whatever the accidental.
22    pub fn step_in_key_signature(&self, altered_pitches: &[Pitch]) -> bool {
23        altered_pitches.iter().any(|p| p.step == self.step)
24    }
25
26    pub(super) fn set_display_status_creating(&mut self, status: bool) {
27        self.accidental
28            .get_or_insert_with(Accidental::natural)
29            .set_display_status(Some(status));
30    }
31
32    /// Records whether the accidental is shown, where there is one to show.
33    fn set_display_status(&mut self, status: bool) {
34        if let Some(accidental) = &mut self.accidental {
35            accidental.set_display_status(Some(status));
36        }
37    }
38
39    fn accidental_name(&self) -> Option<&str> {
40        self.accidental.as_ref().map(Accidental::name)
41    }
42
43    fn accidental_display_type(&self) -> Option<&str> {
44        self.accidental.as_ref().map(Accidental::display_type)
45    }
46
47    fn accidental_display_status(&self) -> Option<bool> {
48        self.accidental
49            .as_ref()
50            .and_then(Accidental::display_status)
51    }
52
53    /// Decides whether this pitch's accidental should be shown, given the
54    /// pitches before it, and records the answer as the accidental's
55    /// `display_status`, adding a natural where a cautionary one is called
56    /// for: music21's `updateAccidentalDisplay`, with the same rules for
57    /// repeats, octaves, ties, key signatures and simultaneities.
58    pub fn update_accidental_display(&mut self, options: &AccidentalDisplayOptions<'_>) {
59        let altered = options.altered_pitches;
60        let past_all: Vec<&Pitch> = options
61            .pitch_past_measure
62            .iter()
63            .chain(options.pitch_past.iter())
64            .collect();
65        let mut display_if_no_previous_accidentals = false;
66
67        if !options.override_status && self.accidental_display_status().is_some() {
68            return;
69        }
70        if self.accidental_display_type() == Some("never") {
71            self.set_display_status(false);
72            return;
73        }
74        if options.last_note_was_tied {
75            let even_tied = self.accidental_display_type() == Some("even-tied");
76            self.set_display_status(even_tied);
77            return;
78        }
79        if options.cautionary_pitch_class
80            && options
81                .other_simultaneous_pitches
82                .iter()
83                .any(|p| p.step == self.step && p.pitch_class() != self.pitch_class())
84        {
85            self.set_display_status_creating(true);
86            return;
87        }
88        if options.cautionary_all
89            || matches!(self.accidental_display_type(), Some("even-tied" | "always"))
90        {
91            self.set_display_status_creating(true);
92            return;
93        }
94        if past_all.is_empty() {
95            if self.accidental.is_some()
96                && (options.override_status || self.accidental_display_status() != Some(true))
97            {
98                let status = if self.accidental_name() == Some("natural") {
99                    self.step_in_key_signature(altered)
100                } else {
101                    !self.name_in_key_signature(altered)
102                };
103                self.set_display_status(status);
104            } else if self.accidental_display_status() == Some(true)
105                && self.name_in_key_signature(altered)
106            {
107                self.set_display_status(false);
108            } else if matches!(self.accidental_name(), None | Some("natural"))
109                && self.step_in_key_signature(altered)
110            {
111                self.set_display_status_creating(true);
112            }
113            return;
114        }
115        for past in options.pitch_past.iter().rev() {
116            if past.step == self.step && past.octave == self.octave {
117                if past.name() != self.name() {
118                    self.set_display_status_creating(true);
119                    return;
120                }
121                break;
122            }
123        }
124        let mut set_from_pitch_past = false;
125        let out_of_measure_length = options.pitch_past_measure.len();
126        let self_name = self.name();
127        let self_name_with_octave = self.name_with_octave();
128        let name_in_key = self.name_in_key_signature(altered);
129        let step_in_key = self.step_in_key_signature(altered);
130        let if_absolutely_necessary =
131            self.accidental_display_type() == Some("if-absolutely-necessary");
132        for i in (0..past_all.len()).rev() {
133            let past_in_measure = i >= out_of_measure_length;
134            let continuous_repeats_in_measure = past_in_measure
135                && past_all[i..]
136                    .iter()
137                    .all(|p| p.name_with_octave() == self_name_with_octave);
138            if !past_in_measure && if_absolutely_necessary {
139                break;
140            }
141            if !past_in_measure
142                && self.accidental.is_some()
143                && self.accidental_name() != Some("natural")
144                && !name_in_key
145            {
146                self.set_display_status(true);
147                return;
148            }
149            let past = past_all[i];
150            if past.step != self.step {
151                continue;
152            }
153            let octave_match = self.octave == past.octave;
154            let past_acc = past.written_accidental();
155            let past_name = past_acc.map(Accidental::name);
156            let past_status = past_acc.and_then(Accidental::display_status);
157            let self_acc_name = self.accidental_name().map(str::to_string);
158            let self_status = self.accidental_display_status();
159
160            if continuous_repeats_in_measure && past_status == Some(true) {
161                self.set_display_status(false);
162                return;
163            } else if continuous_repeats_in_measure
164                && past_acc.is_some()
165                && self_acc_name.is_some()
166                && past_name == self_acc_name.as_deref()
167            {
168                if !name_in_key && (!octave_match || past_status == Some(false)) {
169                    display_if_no_previous_accidentals = true;
170                    continue;
171                }
172                self.set_display_status(false);
173                set_from_pitch_past = true;
174                break;
175            } else if past_name == Some("natural")
176                && (self_acc_name.is_none() || self_acc_name.as_deref() == Some("natural"))
177            {
178                if continuous_repeats_in_measure {
179                    if step_in_key && !octave_match {
180                        self.set_display_status_creating(true);
181                    } else {
182                        self.set_display_status(false);
183                    }
184                } else if step_in_key
185                    && (options.cautionary_not_immediate_repeat || !past_in_measure)
186                {
187                    self.set_display_status_creating(true);
188                } else {
189                    self.set_display_status(false);
190                }
191                set_from_pitch_past = true;
192                break;
193            } else if past_acc.is_some()
194                && past.name() != self_name
195                && past_name != Some("natural")
196                && (self_acc_name.is_none() || self_status == Some(false))
197            {
198                if !octave_match && !options.cautionary_pitch_class {
199                    continue;
200                }
201                if !octave_match && if_absolutely_necessary {
202                    continue;
203                }
204                self.set_display_status_creating(true);
205                set_from_pitch_past = true;
206                break;
207            } else if self_acc_name.is_some()
208                && (((past_acc.is_none() || past_name == Some("natural"))
209                    && self_acc_name.as_deref() != Some("natural"))
210                    || (past_acc.is_some()
211                        && past_name != self_acc_name.as_deref()
212                        && (octave_match || !if_absolutely_necessary)))
213            {
214                self.set_display_status(true);
215                set_from_pitch_past = true;
216                break;
217            } else if past_acc.is_none() && self_acc_name.is_some() {
218                let status = if self_acc_name.as_deref() == Some("natural") {
219                    step_in_key
220                } else {
221                    true
222                };
223                self.set_display_status(status);
224                set_from_pitch_past = true;
225                break;
226            } else if !continuous_repeats_in_measure
227                && past_acc.is_some()
228                && self_acc_name.is_some()
229                && past_name == self_acc_name.as_deref()
230                && octave_match
231            {
232                if !options.cautionary_not_immediate_repeat && past_status != Some(false) {
233                    self.set_display_status(false);
234                    display_if_no_previous_accidentals = false;
235                    set_from_pitch_past = true;
236                    break;
237                } else if past_status == Some(false) {
238                    display_if_no_previous_accidentals = true;
239                } else {
240                    self.set_display_status(!name_in_key);
241                    return;
242                }
243            }
244        }
245        if display_if_no_previous_accidentals {
246            if !name_in_key {
247                self.set_display_status_creating(true);
248            } else {
249                self.set_display_status(false);
250            }
251        } else if !set_from_pitch_past && self.accidental.is_some() {
252            let status = if self.accidental_name() == Some("natural") {
253                step_in_key
254            } else {
255                !name_in_key
256            };
257            self.set_display_status(status);
258        } else if !set_from_pitch_past && step_in_key {
259            self.set_display_status_creating(true);
260        }
261    }
262}
263
264/// The context music21's `updateAccidentalDisplay` reads: the pitches that
265/// came before, in this measure and the previous one, the other pitches
266/// sounding at the same time, the key signature's altered pitches, and the
267/// cautionary-accidental switches, with music21's defaults.
268#[derive(Clone, Debug)]
269pub struct AccidentalDisplayOptions<'a> {
270    /// Pitches preceding this one in the same measure.
271    pub pitch_past: &'a [Pitch],
272    /// Pitches preceding this one in the previous measure.
273    pub pitch_past_measure: &'a [Pitch],
274    /// Other pitches in the same simultaneity.
275    pub other_simultaneous_pitches: &'a [Pitch],
276    /// The key signature's altered pitches.
277    pub altered_pitches: &'a [Pitch],
278    /// Whether a past accidental in any octave calls for a cautionary one.
279    pub cautionary_pitch_class: bool,
280    /// Whether every accidental is shown.
281    pub cautionary_all: bool,
282    /// Whether an already decided `display_status` is decided again.
283    pub override_status: bool,
284    /// Whether an altered pitch shows its accidental again unless it
285    /// immediately repeats.
286    pub cautionary_not_immediate_repeat: bool,
287    /// Whether this pitch follows a tie.
288    pub last_note_was_tied: bool,
289}
290
291impl Default for AccidentalDisplayOptions<'_> {
292    fn default() -> Self {
293        Self {
294            pitch_past: &[],
295            pitch_past_measure: &[],
296            other_simultaneous_pitches: &[],
297            altered_pitches: &[],
298            cautionary_pitch_class: true,
299            cautionary_all: false,
300            override_status: false,
301            cautionary_not_immediate_repeat: true,
302            last_note_was_tied: false,
303        }
304    }
305}