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}