redact_composer_musical/note/
iter.rs1use crate::{Interval, Note, PitchClass};
2use std::collections::Bound;
3use std::ops::RangeBounds;
4
5pub trait NoteIterator {
10 fn iter_notes_in_range<R: RangeBounds<Note>>(&self, note_range: R) -> NoteIter<R>;
12 fn notes_in_range<R: RangeBounds<Note>>(&self, note_range: R) -> Vec<Note> {
14 self.iter_notes_in_range(note_range).collect()
15 }
16}
17
18#[derive(Debug, Clone)]
29pub struct NoteIter<R: RangeBounds<Note>> {
30 curr: Option<Note>,
31 intervals: Vec<Interval>,
32 interval_idx: usize,
33 bounds: R,
34}
35
36impl<R: RangeBounds<Note>> NoteIter<R> {
37 pub fn chromatic(range: R) -> NoteIter<R> {
39 NoteIter {
40 curr: match range.start_bound() {
41 Bound::Included(n) => Some(*n),
42 Bound::Excluded(n) => Some(Note(n.0 + 1)),
43 Bound::Unbounded => Some(Note(0)),
44 },
45 intervals: vec![Interval(1)],
46 interval_idx: 0,
47 bounds: range,
48 }
49 }
50
51 fn next_interval(&mut self) -> Option<Interval> {
52 let returned = self
53 .intervals
54 .get(self.interval_idx)
55 .copied()
56 .and_then(|i| {
57 if self.curr.map(|n| u8::MAX - n.0 >= i.0).unwrap_or(true) {
59 Some(i)
60 } else {
61 None
62 }
63 });
64
65 self.interval_idx = (self.interval_idx + 1) % self.intervals.len();
66
67 returned
68 }
69}
70
71impl<R: RangeBounds<Note>> From<R> for NoteIter<R> {
72 fn from(note_range: R) -> Self {
73 NoteIter::chromatic(note_range)
74 }
75}
76
77impl<R: RangeBounds<Note>> From<(PitchClass, Vec<Interval>, R)> for NoteIter<R> {
78 fn from(value: (PitchClass, Vec<Interval>, R)) -> Self {
81 let (root, intervals, note_range) = value;
82 if let Some(first) = intervals.first() {
83 let start_pitch_class = root + *first;
84 let mut steps = intervals.windows(2).fold(vec![], |mut acc, i| {
85 acc.push(Interval(i[1].0 - i[0].0));
86
87 acc
88 });
89
90 if steps.is_empty() {
91 steps.push(Interval::Octave)
92 } else {
93 let sum_interval = steps.iter().copied().sum::<Interval>();
94 if sum_interval.to_simple() != Interval::P1 {
95 steps.push(sum_interval.to_simple().inversion())
96 }
97 }
98
99 let first_range_note = match note_range.start_bound() {
100 Bound::Included(n) => *n,
101 Bound::Excluded(n) => Note(n.0 + 1),
102 Bound::Unbounded => Note(0),
103 };
104
105 let (first_note, interval_idx_offset) = {
108 let mut interval_step_idx = steps.len() - 1;
109 let mut starting_pitch = start_pitch_class;
110 let mut interval_offset = Interval::P1;
111 let mut closest_starting_distance =
112 first_range_note.pitch_class().interval_to(&starting_pitch);
113
114 while interval_step_idx > 0
117 && first_range_note.pitch_class().interval_to(
118 &(starting_pitch + steps[interval_step_idx].inversion().to_simple()),
119 ) < closest_starting_distance
120 && interval_offset + steps[interval_step_idx] < Interval::Octave
121 {
122 starting_pitch += steps[interval_step_idx].inversion().to_simple();
123 interval_offset += steps[interval_step_idx];
124 closest_starting_distance =
125 first_range_note.pitch_class().interval_to(&starting_pitch);
126 interval_step_idx -= 1;
127 }
128
129 let starting_note = starting_pitch.at_or_above(&first_range_note);
130 if note_range.contains(&starting_note) {
131 (Some(starting_note), (interval_step_idx + 1) % steps.len())
132 } else {
133 (None, 0)
134 }
135 };
136
137 NoteIter {
138 curr: first_note,
139 intervals: steps,
140 interval_idx: interval_idx_offset,
141 bounds: note_range,
142 }
143 } else {
144 NoteIter {
146 curr: None,
147 intervals: Vec::new(),
148 interval_idx: Default::default(),
149 bounds: note_range,
150 }
151 }
152 }
153}
154
155impl<R> Iterator for NoteIter<R>
156where
157 R: RangeBounds<Note>,
158{
159 type Item = Note;
160
161 fn next(&mut self) -> Option<Self::Item> {
162 if let Some(note) = self.curr {
163 let return_note = if self.bounds.contains(¬e) {
164 Some(note)
165 } else {
166 None
167 };
168
169 self.curr = self.next_interval().map(|interval| note + interval);
170
171 return_note
172 } else {
173 None
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use crate::{
181 Chord, ChordShape, Interval, IntervalCollection, Key, Mode, Note, NoteIterator, PitchClass,
182 PitchClassCollection, Scale,
183 };
184
185 #[test]
186 fn key_notes_boundary_test() {
187 let roots = vec![PitchClass(0), PitchClass(9), PitchClass(11)];
188 let scales = Scale::values();
189 let modes = vec![Mode::Ionian, Mode::Dorian, Mode::Aeolian, Mode::Locrian];
190 let lengths = [0, 1, 11, 12, 13, 23];
191 let offsets = [0, 1, 11, 12, 13, 23];
192
193 for root in roots.clone() {
195 for scale in scales.clone() {
196 for mode in modes.clone() {
197 for length in lengths {
198 for offset in offsets {
199 let key = Key::from((root, scale, mode));
200 let key_pitches = key.pitch_classes();
201
202 let note_range = Note(offset)..Note(offset + length);
203 let output = key.notes_in_range(note_range.clone());
204 let out_of_key = output
205 .iter()
206 .filter(|n| !key_pitches.contains(&n.pitch_class()))
207 .collect::<Vec<_>>();
208 assert!(
209 out_of_key.is_empty(),
210 "`{:?}.notes_in_range({:?})` produced out of key notes.\nOutput: {:?}\nOut of key: {:?}",
211 key, note_range, output, out_of_key
212 )
213 }
214 }
215 }
216 }
217 }
218 }
219
220 #[test]
221 fn chord_note_iter_smoke_test() {
222 let roots = [PitchClass(0), PitchClass(7), PitchClass(11)];
223 let chord_shapes = ChordShape::all();
224 let range_lens = (0..=36).collect::<Vec<_>>();
225 let range_offsets = (0..=12).collect::<Vec<_>>();
226
227 for root in roots {
228 for shape in chord_shapes.clone() {
229 for range_len in range_lens.clone() {
230 for range_offset in range_offsets.clone() {
231 let first_range_note = Note(range_offset);
232 let note_range = first_range_note..(first_range_note + Interval(range_len));
233 let chord = Chord::from((root, shape));
234 let chord_pitches = chord.pitch_classes();
235 let chord_notes = chord.notes_in_range(note_range.clone());
236
237 if note_range
238 .clone()
239 .contains(&chord.root.at_or_above(&first_range_note))
240 {
241 assert!(
242 chord_notes.contains(&chord.root.at_or_above(&first_range_note)),
243 "{:?} could contain chord root of {:?}, but it doesn't.",
244 note_range,
245 chord
246 );
247 }
248
249 assert!(
250 chord_notes
251 .iter()
252 .all(|n| chord_pitches.contains(&n.pitch_class())),
253 "{:?}.notes_in_range({:?}) produced notes not within the chord.",
254 chord,
255 note_range
256 );
257
258 let note_count_lower_bound = range_len as usize
259 / (first_range_note.pitch_class().interval_to(&chord.root)
260 + *chord.intervals().last().unwrap()
261 + chord.intervals().last().unwrap().inversion())
262 .0 as usize
263 * chord.intervals().len();
264
265 assert!(chord_notes.len() >= note_count_lower_bound,
266 "{:?} has room for at least {:?} notes of {:?}, but only {:?} were produced.",
267 note_range, note_count_lower_bound, chord, chord_notes.len());
268
269 if note_count_lower_bound >= chord.intervals().len() {
270 assert!(
271 chord_pitches.iter().all(|p| {
272 chord_notes.iter().any(|n| n.pitch_class() == *p)
273 }),
274 "{:?} has room for all notes of {:?}, but they weren't all produced.", note_range, chord);
275 }
276 }
277 }
278 }
279 }
280 }
281}