Skip to main content

music21_rs/stream/
selection.rs

1//! Keeping or taking out a stream's elements: music21's `extractContext`,
2//! `removeByClass` and `removeByNotOfClass`.
3
4use super::{Stream, StreamElement, StreamEvent};
5use crate::defaults::FloatType;
6use crate::error::{Error, Result};
7use std::cmp::Ordering;
8
9use super::exact::Exact;
10
11impl Stream {
12    /// A stream of the elements this stream holds itself that start from
13    /// `before` quarters before the element at `index` to `after` quarters
14    /// after it ends: music21's `extractContext`. The stream keeps its kind
15    /// and id; the elements keep their offsets, and spanners theirs.
16    ///
17    /// ```
18    /// use music21_rs::tinynotation::from_tiny_notation;
19    ///
20    /// let line = from_tiny_notation("4/4 c4 d4 e4 f4 g4 a4")?.flatten();
21    /// let index = line.events().iter().position(|event| event.offset() == 2.0).unwrap();
22    /// let around = line.extract_context(index, 1.0, 1.0)?;
23    /// let offsets: Vec<f64> = around.notes().iter().map(|(offset, _)| *offset).collect();
24    /// assert_eq!(offsets, [1.0, 2.0, 3.0]);
25    /// # Ok::<(), music21_rs::Error>(())
26    /// ```
27    ///
28    /// # Errors
29    ///
30    /// An index this stream holds no element at.
31    pub fn extract_context(
32        &self,
33        index: usize,
34        before: FloatType,
35        after: FloatType,
36    ) -> Result<Stream> {
37        let found = self
38            .events
39            .get(index)
40            .ok_or_else(|| Error::Stream("Could not find the element in the stream".to_string()))?;
41        // The bounds are worked out as Python works them out from the
42        // numbers music21 holds, and offsets compared with them exactly.
43        let at = Exact::held(found.offset());
44        let start = at.plus(Exact::of_float(-before));
45        let end = at
46            .plus(Exact::held(found.element().quarter_length()))
47            .plus(Exact::of_float(after));
48        let within = |offset: FloatType| {
49            let offset = Exact::held(offset);
50            start.cmp(offset) != Ordering::Greater && offset.cmp(end) == Ordering::Less
51        };
52        let mut out = self.clone_empty();
53        out.labels.spanners = self.labels.spanners.clone();
54        if within(0.0) {
55            out.labels.left_barline = self.labels.left_barline.clone();
56        }
57        if within(self.end_offset()) {
58            out.labels.right_barline = self.labels.right_barline.clone();
59        }
60        let kept: Vec<(StreamEvent, Option<usize>)> = self
61            .events
62            .iter()
63            .enumerate()
64            .filter(|(_, event)| within(event.offset()))
65            .map(|(index, event)| (event.clone(), Some(index)))
66            .collect();
67        out.events = self.events.clone();
68        out.replace_events(kept);
69        Ok(out)
70    }
71
72    /// Takes out the elements this stream holds itself that are of one of
73    /// `classes`, by music21's class names: music21's `removeByClass`. A
74    /// spanner names nothing for an element taken out.
75    ///
76    /// ```
77    /// use music21_rs::tinynotation::from_tiny_notation;
78    ///
79    /// let mut line = from_tiny_notation("4/4 c4 r4 e2")?.flatten();
80    /// line.remove_by_class(&["Rest", "TimeSignature"]);
81    /// assert!(line.events().iter().all(|event| !event.element().is_instance("Rest")));
82    /// assert_eq!(line.notes().len(), 2);
83    /// assert!(line.time_signatures(false)?.is_empty());
84    /// # Ok::<(), music21_rs::Error>(())
85    /// ```
86    pub fn remove_by_class(&mut self, classes: &[&str]) {
87        self.remove_where(|element| classes.iter().any(|class| element.is_instance(class)));
88    }
89
90    /// Takes out the elements this stream holds itself that are of none of
91    /// `classes`: music21's `removeByNotOfClass`.
92    pub fn remove_by_not_of_class(&mut self, classes: &[&str]) {
93        self.remove_where(|element| !classes.iter().any(|class| element.is_instance(class)));
94    }
95
96    fn remove_where(&mut self, gone: impl Fn(&StreamElement) -> bool) {
97        let barline_gone = |barline: &Option<crate::bar::Barline>| {
98            barline
99                .as_ref()
100                .is_some_and(|barline| gone(&StreamElement::Barline(barline.clone())))
101        };
102        if barline_gone(&self.labels.left_barline) {
103            self.labels.left_barline = None;
104        }
105        if barline_gone(&self.labels.right_barline) {
106            self.labels.right_barline = None;
107        }
108        let kept: Vec<(StreamEvent, Option<usize>)> = self
109            .events
110            .iter()
111            .enumerate()
112            .filter(|(_, event)| !gone(event.element()))
113            .map(|(index, event)| (event.clone(), Some(index)))
114            .collect();
115        self.replace_events(kept);
116    }
117}