Skip to main content

music21_rs/stream/
editing.rs

1//! Changing a stream as music21's editing methods change it: ties carried
2//! on, offsets and lengths scaled, notes inverted, elements shifted,
3//! repeated or put in with room made for them, and a pickup padded.
4
5use super::exact::{Exact, at_offset};
6use super::{Stream, StreamElement, StreamEvent};
7use crate::defaults::{FloatType, IntegerType};
8use crate::duration::Duration;
9use crate::error::{Error, Result};
10use crate::key::KeySignature;
11use crate::makenotation::{event_order, op_frac};
12use crate::meter::{TimeSignature, best_time_signature};
13use crate::notation::{Tie, TieType};
14use crate::pitch::Pitch;
15
16fn stream_error(message: impl Into<String>) -> Error {
17    Error::Stream(message.into())
18}
19
20/// Where [`Stream::scale_offsets`] holds still while it scales: music21's
21/// `anchorZero`.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Anchor {
24    /// The first element stays where it is: `'lowest'`.
25    Lowest,
26    /// The last element stays where it is: `'highest'`.
27    Highest,
28    /// Offset nought stays where it is: `None`.
29    Zero,
30}
31
32/// Whether a flattened element sorts before another, as music21's
33/// `flatten` sorts them: by offset, by class, grace notes first.
34fn flat_order(
35    a: (FloatType, &StreamElement),
36    b: (FloatType, &StreamElement),
37) -> std::cmp::Ordering {
38    let grace = |element: &StreamElement| element.duration().is_some_and(Duration::is_grace);
39    a.0.total_cmp(&b.0)
40        .then(a.1.class_sort_order().cmp(&b.1.class_sort_order()))
41        .then(grace(b.1).cmp(&grace(a.1)))
42}
43
44/// One pitch a note or chord sounds, as [`Stream::extend_ties`] ties it:
45/// the leaf it is in, which note of a chord, and the tie it has now.
46#[derive(Clone, Debug)]
47struct Sounded {
48    leaf: usize,
49    note: usize,
50    name: String,
51    tie: Option<Tie>,
52    /// Whether the tie can be kept: a chord symbol's notes are not.
53    kept: bool,
54}
55
56fn sounded(leaf: usize, element: &StreamElement) -> Option<Vec<Sounded>> {
57    let of = |note: usize, pitch: &Pitch, tie: Option<&Tie>, kept: bool| Sounded {
58        leaf,
59        note,
60        name: pitch.name_with_octave(),
61        tie: tie.cloned(),
62        kept,
63    };
64    Some(match element {
65        StreamElement::Note(note) => vec![of(0, note.pitch(), note.tie(), true)],
66        StreamElement::Chord(chord) => chord
67            .notes()
68            .iter()
69            .enumerate()
70            .map(|(index, note)| of(index, note.pitch(), note.tie(), true))
71            .collect(),
72        StreamElement::ChordSymbol(symbol) => symbol
73            .pitches()
74            .unwrap_or_default()
75            .iter()
76            .enumerate()
77            .map(|(index, pitch)| of(index, pitch, None, false))
78            .collect(),
79        _ => return None,
80    })
81}
82
83impl Stream {
84    /// Ties each note to a note of the same name and octave that starts
85    /// where it ends, a chord's notes each to its own, as music21's
86    /// `extendTies` does. With `ignore_rests`, a note is tied to the next
87    /// note whatever lies between.
88    ///
89    /// ```
90    /// use music21_rs::tinynotation::from_tiny_notation;
91    ///
92    /// let mut line = from_tiny_notation("4/4 c4 c4 d4 d4")?;
93    /// line.extend_ties(false);
94    /// let ties: Vec<_> = line
95    ///     .notes()
96    ///     .iter()
97    ///     .map(|(_, note)| match note {
98    ///         music21_rs::StreamElement::Note(note) => note.tie().map(|tie| tie.tie_type()),
99    ///         _ => None,
100    ///     })
101    ///     .collect();
102    /// assert_eq!(ties[0], Some(music21_rs::notation::TieType::Start));
103    /// assert_eq!(ties[1], Some(music21_rs::notation::TieType::Stop));
104    /// # Ok::<(), music21_rs::Error>(())
105    /// ```
106    pub fn extend_ties(&mut self, ignore_rests: bool) {
107        // music21's `flatten().notes`: the notes and chords, flattened and
108        // sorted, each with the leaf it is.
109        let leaves = self.leaves();
110        let mut flat: Vec<(usize, FloatType, &StreamElement)> = leaves
111            .iter()
112            .enumerate()
113            .filter(|(_, (_, element))| element.is_instance("NotRest"))
114            .map(|(leaf, (offset, element))| (leaf, *offset, *element))
115            .collect();
116        flat.sort_by(|a, b| flat_order((a.1, a.2), (b.1, b.2)));
117        let mut pitches: Vec<Option<Vec<Sounded>>> = flat
118            .iter()
119            .map(|(leaf, _, element)| sounded(*leaf, element))
120            .collect();
121        for i in 0..flat.len() {
122            if pitches[i].is_none() || i + 1 == flat.len() {
123                continue;
124            }
125            // Where music21 looks: the note's offset and length added as
126            // Python adds them, or with `ignore_rests` the next note's.
127            let target = if ignore_rests {
128                Exact::held(flat[i + 1].1)
129            } else {
130                Exact::held(flat[i].1).plus(Exact::held(flat[i].2.quarter_length()))
131            };
132            let connections: Vec<usize> = (0..flat.len())
133                .filter(|&j| at_offset(flat[j].1, flat[j].2.quarter_length(), target))
134                .collect();
135            let sources = pitches[i].as_ref().map_or(0, Vec::len);
136            for p in 0..sources {
137                for &m in &connections {
138                    let count = pitches[m].as_ref().map_or(0, Vec::len);
139                    for q in 0..count {
140                        let (ours, theirs) = match (&pitches[i], &pitches[m]) {
141                            (Some(ours), Some(theirs)) => (&ours[p].name, &theirs[q].name),
142                            _ => continue,
143                        };
144                        if ours != theirs {
145                            continue;
146                        }
147                        if let Some(sources) = pitches[i].as_mut() {
148                            let source = &mut sources[p];
149                            match source.tie.as_mut() {
150                                None => source.tie = Some(Tie::new(TieType::Start)),
151                                Some(tie) if tie.tie_type() == TieType::Stop => {
152                                    tie.set_tie_type(TieType::Continue);
153                                }
154                                Some(_) => {}
155                            }
156                        }
157                        if let Some(targets) = pitches[m].as_mut() {
158                            targets[q].tie = Some(Tie::new(TieType::Stop));
159                        }
160                        break;
161                    }
162                }
163            }
164        }
165        let mut ties: std::collections::HashMap<(usize, usize), Option<Tie>> =
166            std::collections::HashMap::new();
167        for list in pitches.into_iter().flatten() {
168            for sounded in list.into_iter().filter(|sounded| sounded.kept) {
169                ties.insert((sounded.leaf, sounded.note), sounded.tie);
170            }
171        }
172        let mut leaf = 0;
173        self.for_each_mut(&mut |_, element| {
174            match element {
175                StreamElement::Note(note) => {
176                    if let Some(tie) = ties.get(&(leaf, 0)) {
177                        note.set_tie(tie.clone());
178                    }
179                }
180                StreamElement::Chord(chord) => {
181                    for (index, note) in chord.notes_mut().iter_mut().enumerate() {
182                        if let Some(tie) = ties.get(&(leaf, index)) {
183                            note.set_tie(tie.clone());
184                        }
185                    }
186                }
187                _ => {}
188            }
189            leaf += 1;
190        });
191    }
192
193    /// A copy of the stream with each element's offset scaled by `amount`
194    /// from the anchor, and those of the streams it holds from theirs, as
195    /// music21's `scaleOffsets` does.
196    ///
197    /// # Errors
198    ///
199    /// An amount of nought or less.
200    pub fn scale_offsets(
201        &self,
202        amount: FloatType,
203        anchor: Anchor,
204        nested: Anchor,
205    ) -> Result<Stream> {
206        if amount.partial_cmp(&0.0) != Some(std::cmp::Ordering::Greater) {
207            return Err(stream_error("amountToScale must be greater than zero"));
208        }
209        let mut out = self.clone();
210        out.scale_offsets_in_place(amount, anchor, nested);
211        Ok(out)
212    }
213
214    fn scale_offsets_in_place(&mut self, amount: FloatType, anchor: Anchor, nested: Anchor) {
215        let shift = match anchor {
216            Anchor::Lowest => self.lowest_offset(),
217            Anchor::Highest => self.highest_offset(),
218            Anchor::Zero => 0.0,
219        };
220        for event in self.events_mut() {
221            let offset = op_frac((event.offset() - shift) * amount + shift);
222            let mut element = event.element().clone();
223            if let StreamElement::Stream(inner) = &mut element {
224                inner.scale_offsets_in_place(amount, nested, nested);
225            }
226            *event = StreamEvent::new(offset, element);
227        }
228    }
229
230    /// A copy of the stream with every length scaled by `amount`, those in
231    /// the streams it holds too: music21's `scaleDurations`.
232    ///
233    /// # Errors
234    ///
235    /// An amount of nought or less, or a length no duration has.
236    pub fn scale_durations(&self, amount: FloatType) -> Result<Stream> {
237        if amount.partial_cmp(&0.0) != Some(std::cmp::Ordering::Greater) {
238            return Err(stream_error("amountToScale must be greater than zero"));
239        }
240        let mut out = self.clone();
241        let mut failed = None;
242        out.for_each_mut(&mut |_, element| {
243            if failed.is_some() {
244                return;
245            }
246            if let Err(error) = scale_duration(element, amount) {
247                failed = Some(error);
248            }
249        });
250        failed.map_or(Ok(out), Err)
251    }
252
253    /// A copy of the stream with its offsets scaled from the first element
254    /// and its lengths scaled, by `amount`: music21's
255    /// `augmentOrDiminish`.
256    ///
257    /// ```
258    /// use music21_rs::tinynotation::from_tiny_notation;
259    ///
260    /// let line = from_tiny_notation("4/4 c4 d8 e8")?;
261    /// let longer = line.augment_or_diminish(2.0)?;
262    /// assert_eq!(longer.notes()[2].0, 3.0);
263    /// # Ok::<(), music21_rs::Error>(())
264    /// ```
265    ///
266    /// # Errors
267    ///
268    /// As [`Stream::scale_durations`].
269    pub fn augment_or_diminish(&self, amount: FloatType) -> Result<Stream> {
270        self.scale_offsets(amount, Anchor::Lowest, Anchor::Zero)?
271            .scale_durations(amount)
272    }
273
274    /// A copy of the stream with each note turned upside down about
275    /// `inversion` (C4 where none is given) by step, its accidental that of
276    /// the key -- the stream's one key signature, the one in force at the
277    /// note where it has several, C major where it has none: music21's
278    /// `invertDiatonic`. A chord's notes are left as they are.
279    ///
280    /// # Errors
281    ///
282    /// A note turned past the steps a pitch can stand on.
283    pub fn invert_diatonic(&self, inversion: Option<&Pitch>) -> Result<Stream> {
284        let inversion = match inversion {
285            Some(pitch) => pitch.diatonic_note_number(),
286            None => Pitch::from_name("C4")?.diatonic_note_number(),
287        };
288        let signatures: Vec<KeySignature> = self
289            .recurse()
290            .into_iter()
291            .filter_map(|(_, element)| signature_of(element))
292            .collect();
293        let only = match signatures.len() {
294            0 => Some(KeySignature::new(0)),
295            1 => signatures.into_iter().next(),
296            _ => None,
297        };
298        // The key signature in force at each note, in the order music21
299        // finds them.
300        let leaves = self.leaves();
301        let mut order: Vec<usize> = (0..leaves.len()).collect();
302        order.sort_by(|a, b| flat_order(leaves[*a], leaves[*b]));
303        let mut in_force: std::collections::HashMap<usize, KeySignature> =
304            std::collections::HashMap::new();
305        let mut current: Option<KeySignature> = None;
306        for leaf in order {
307            if let Some(signature) = signature_of(leaves[leaf].1) {
308                current = Some(signature);
309            }
310            if let Some(signature) = &current {
311                in_force.insert(leaf, signature.clone());
312            }
313        }
314        let mut out = self.clone();
315        let mut leaf = 0;
316        let mut failed = None;
317        out.for_each_mut(&mut |_, element| {
318            if let StreamElement::Note(note) = element
319                && failed.is_none()
320            {
321                let signature = only.as_ref().or_else(|| in_force.get(&leaf));
322                if let Err(error) = invert_note(note, inversion, signature) {
323                    failed = Some(error);
324                }
325            }
326            leaf += 1;
327        });
328        failed.map_or(Ok(out), Err)
329    }
330
331    /// Moves the elements this stream holds itself, those starting at or
332    /// after `start` and before `end` where given, by `offset`: music21's
333    /// `shiftElements`.
334    pub fn shift_elements(
335        &mut self,
336        offset: FloatType,
337        start: Option<FloatType>,
338        end: Option<FloatType>,
339    ) {
340        let events: Vec<(StreamEvent, Option<usize>)> = self
341            .events()
342            .iter()
343            .enumerate()
344            .map(|(index, event)| {
345                let at = event.offset();
346                let moves = start.is_none_or(|start| at >= start) && end.is_none_or(|end| at < end);
347                let offset = if moves { op_frac(at + offset) } else { at };
348                (
349                    StreamEvent::new(offset, event.element().clone()),
350                    Some(index),
351                )
352            })
353            .collect();
354        self.reorder_events(events);
355    }
356
357    /// Puts the events in place of this stream's own in music21's order,
358    /// those that sort alike in the order given.
359    fn reorder_events(&mut self, mut events: Vec<(StreamEvent, Option<usize>)>) {
360        events.sort_by(|a, b| event_order(&a.0, &b.0));
361        self.replace_events(events);
362    }
363
364    /// Appends a copy of `element` this many times, each after the last:
365    /// music21's `repeatAppend`.
366    pub fn repeat_append(&mut self, element: &StreamElement, times: usize) {
367        for _ in 0..times {
368            let offset = self.end_offset();
369            self.insert_sorted(vec![StreamEvent::new(offset, element.clone())]);
370        }
371    }
372
373    /// Puts a copy of `element` at each of `offsets`: music21's
374    /// `repeatInsert`.
375    pub fn repeat_insert(&mut self, element: &StreamElement, offsets: &[FloatType]) {
376        self.insert_sorted(
377            offsets
378                .iter()
379                .map(|offset| StreamEvent::new(op_frac(*offset), element.clone()))
380                .collect(),
381        );
382    }
383
384    /// Puts `element` at `offset`, first moving everything from the
385    /// element nearest after that offset onwards far enough that it starts
386    /// where the new element ends: music21's `insertAndShift`.
387    pub fn insert_and_shift(&mut self, offset: FloatType, element: StreamElement) {
388        let end = offset + element.quarter_length();
389        let nearest = self
390            .events()
391            .iter()
392            .map(StreamEvent::offset)
393            .filter(|at| at - offset >= 0.0)
394            .fold(None, |best: Option<FloatType>, at| {
395                Some(best.map_or(at, |best| {
396                    if at - offset < best - offset {
397                        at
398                    } else {
399                        best
400                    }
401                }))
402            });
403        let shift = nearest.map_or(0.0, |nearest| end - nearest);
404        if shift > 0.0 {
405            let events: Vec<(StreamEvent, Option<usize>)> = self
406                .events()
407                .iter()
408                .enumerate()
409                .map(|(index, event)| {
410                    let at = event.offset();
411                    let moved = if at - offset >= 0.0 {
412                        op_frac(at + shift)
413                    } else {
414                        at
415                    };
416                    (
417                        StreamEvent::new(moved, event.element().clone()),
418                        Some(index),
419                    )
420                })
421                .collect();
422            self.reorder_events(events);
423        }
424        self.insert_sorted(vec![StreamEvent::new(op_frac(offset), element)]);
425    }
426
427    /// How much of its bar a measure fills, as a proportion: music21's
428    /// `barDurationProportion`. The bar is that of the measure's own time
429    /// signature, or of `meter` where it has none; failing both, of the time
430    /// signature that best fits its notes
431    /// ([`best_time_signature`](crate::meter::best_time_signature)), and
432    /// failing that the measure's own length.
433    ///
434    /// # Errors
435    ///
436    /// A time signature whose bar cannot be worked out, or a bar of no
437    /// length: an empty measure with no time signature.
438    pub fn bar_duration_proportion(&self, meter: Option<&TimeSignature>) -> Result<FloatType> {
439        let bar = self.bar_length(meter)?;
440        self.proportion_of(bar)
441    }
442
443    fn proportion_of(&self, bar: FloatType) -> Result<FloatType> {
444        if bar == 0.0 {
445            return Err(stream_error("a bar of no length"));
446        }
447        Ok(op_frac(self.end_offset() / bar))
448    }
449
450    fn bar_length(&self, meter: Option<&TimeSignature>) -> Result<FloatType> {
451        let own = self
452            .events()
453            .iter()
454            .take_while(|event| event.offset() == 0.0)
455            .find_map(|event| match event.element() {
456                StreamElement::TimeSignature(found) => Some(found.clone()),
457                _ => None,
458            });
459        let meter = match (own, meter) {
460            (Some(own), _) => own,
461            (None, Some(given)) => given.clone(),
462            (None, None) => match best_time_signature(self) {
463                Ok(best) => best,
464                Err(_) => return Ok(self.end_offset()),
465            },
466        };
467        Ok(meter.bar_quarter_length())
468    }
469
470    /// Pads a measure that does not fill its bar on the left by what it
471    /// lacks, as a pickup: music21's `padAsAnacrusis`. With
472    /// `use_initial_rests`, the rests it starts with are taken out first
473    /// and what follows moved back. The bar is as
474    /// [`Stream::bar_duration_proportion`] has it.
475    ///
476    /// # Errors
477    ///
478    /// A time signature whose bar cannot be worked out.
479    pub fn pad_as_anacrusis(
480        &mut self,
481        meter: Option<&TimeSignature>,
482        use_initial_rests: bool,
483    ) -> Result<()> {
484        if use_initial_rests {
485            let rests: Vec<usize> = self
486                .events()
487                .iter()
488                .enumerate()
489                .filter(|(_, event)| event.element().is_instance("GeneralNote"))
490                .take_while(|(_, event)| matches!(event.element(), StreamElement::Rest(_)))
491                .map(|(index, _)| index)
492                .collect();
493            if !rests.is_empty() {
494                self.remove_shifting(&rests);
495            }
496        }
497        let bar = self.bar_length(meter)?;
498        let proportion = self.proportion_of(bar)?;
499        if proportion < 1.0 {
500            self.set_padding_left(op_frac(bar * (1.0 - proportion)));
501        }
502        Ok(())
503    }
504
505    /// Takes out these events, in order, each moving what follows it up to
506    /// the next back by all that has been taken out: music21's `remove`
507    /// with `shiftOffsets`.
508    fn remove_shifting(&mut self, indices: &[usize]) {
509        let mut offsets: Vec<FloatType> = self.events().iter().map(StreamEvent::offset).collect();
510        let mut gone = vec![false; offsets.len()];
511        let mut shift = 0.0;
512        for (i, &index) in indices.iter().enumerate() {
513            gone[index] = true;
514            let length = self.events()[index].element().quarter_length();
515            let region_start = offsets[index] + length;
516            let region_end = match indices.get(i + 1) {
517                Some(&next) => offsets[next],
518                None => self
519                    .events()
520                    .iter()
521                    .zip(&offsets)
522                    .zip(&gone)
523                    .filter(|(_, gone)| !**gone)
524                    .map(|((event, offset), _)| op_frac(offset + event.element().quarter_length()))
525                    .fold(0.0, FloatType::max),
526            };
527            shift += length;
528            if shift != 0.0 {
529                let span = super::span::OffsetSpan {
530                    include_end_boundary: false,
531                    ..super::span::OffsetSpan::between(region_start, region_end)
532                };
533                for (index, at) in offsets.iter_mut().enumerate() {
534                    if gone[index] {
535                        continue;
536                    }
537                    if span.takes_at(*at, self.events[index].element().quarter_length()) {
538                        *at = op_frac(*at - shift);
539                    }
540                }
541            }
542        }
543        let events: Vec<(StreamEvent, Option<usize>)> = self
544            .events()
545            .iter()
546            .enumerate()
547            .filter(|(index, _)| !gone[*index])
548            .map(|(index, event)| {
549                (
550                    StreamEvent::new(offsets[index], event.element().clone()),
551                    Some(index),
552                )
553            })
554            .collect();
555        self.reorder_events(events);
556    }
557}
558
559fn signature_of(element: &StreamElement) -> Option<KeySignature> {
560    match element {
561        StreamElement::KeySignature(signature) => Some(signature.clone()),
562        StreamElement::Key(key) => Some(key.key_signature()),
563        _ => None,
564    }
565}
566
567fn invert_note(
568    note: &mut crate::note::Note,
569    inversion: IntegerType,
570    signature: Option<&KeySignature>,
571) -> Result<()> {
572    let mut pitch = note.pitch().clone();
573    let number = 2 * inversion - pitch.diatonic_note_number();
574    pitch.set_diatonic_note_number(number)?;
575    if let Some(signature) = signature {
576        let step = pitch.name().chars().next().unwrap_or('C');
577        let mut accidental = signature.accidental_by_step(step)?;
578        if let Some(accidental) = accidental.as_mut() {
579            accidental.set_display_status(None);
580        }
581        pitch.set_written_accidental(accidental);
582    }
583    note.set_pitch(pitch);
584    Ok(())
585}
586
587/// Scales an element's length, as music21's `augmentOrDiminish` of its
588/// duration.
589fn scale_duration(element: &mut StreamElement, amount: FloatType) -> Result<()> {
590    map_duration(element, &mut |duration| {
591        // An element given no length of its own has none to scale.
592        duration
593            .map(|duration| duration.augment_or_diminish(amount))
594            .transpose()
595    })
596}
597
598/// Writes a new length on an element, worked out from the one it carries,
599/// which is nothing where it has been given none: the marks that only say
600/// what is in force from a point -- a key, a metre, a tempo -- never have
601/// one, and a note or rest need not have been given one yet. Answering
602/// `None` leaves the element as it stands.
603pub(crate) fn map_duration(
604    element: &mut StreamElement,
605    change: &mut impl FnMut(Option<&Duration>) -> Result<Option<Duration>>,
606) -> Result<()> {
607    match element {
608        StreamElement::Note(note) => {
609            if let Some(next) = change(note.duration())? {
610                note.set_duration(next);
611            }
612        }
613        StreamElement::Chord(chord) => {
614            if let Some(next) = change(chord.duration())? {
615                chord.set_duration(next);
616            }
617        }
618        StreamElement::Rest(rest) => {
619            if let Some(next) = change(Some(rest.duration()))? {
620                rest.set_duration(next);
621            }
622        }
623        StreamElement::Unpitched(stroke) => {
624            if let Some(next) = change(stroke.written().duration())? {
625                stroke.written_mut().set_duration(next);
626            }
627        }
628        StreamElement::PercussionChord(chord) => {
629            if let Some(next) = change(chord.written().duration())? {
630                chord.written_mut().set_duration(next);
631            }
632        }
633        StreamElement::ChordSymbol(symbol) => {
634            if let Some(next) = change(Some(symbol.duration()))? {
635                symbol.set_duration(next);
636            }
637        }
638        _ => {}
639    }
640    Ok(())
641}
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646    use crate::note::Note;
647    use crate::rest::Rest;
648    use crate::stream::StreamKind;
649
650    fn note(name: &str, quarter_length: FloatType) -> Result<StreamElement> {
651        Ok(Note::from_name(name)?
652            .with_duration(Duration::new(quarter_length)?)
653            .into())
654    }
655
656    fn placed(stream: &Stream) -> Vec<(String, FloatType)> {
657        stream
658            .events()
659            .iter()
660            .filter_map(|event| match event.element() {
661                StreamElement::Note(note) => {
662                    Some((note.pitch().name_with_octave(), event.offset()))
663                }
664                StreamElement::Rest(_) => Some(("rest".to_string(), event.offset())),
665                _ => None,
666            })
667            .collect()
668    }
669
670    fn named(pairs: &[(&str, FloatType)]) -> Vec<(String, FloatType)> {
671        pairs
672            .iter()
673            .map(|(name, offset)| ((*name).to_string(), *offset))
674            .collect()
675    }
676
677    // The expected values are music21's, from the same streams.
678
679    #[test]
680    fn insert_and_shift_makes_room() -> Result<()> {
681        let mut stream = Stream::new();
682        stream.insert(0.0, note("C4", 2.0)?);
683        stream.insert(2.0, note("D4", 2.0)?);
684        stream.insert_and_shift(1.0, note("E4", 2.0)?);
685        assert_eq!(
686            placed(&stream),
687            named(&[("C4", 0.0), ("E4", 1.0), ("D4", 3.0)])
688        );
689        Ok(())
690    }
691
692    #[test]
693    fn shift_elements_moves_a_range() -> Result<()> {
694        let mut stream = Stream::new();
695        for (offset, name) in ["C4", "D4", "E4", "F4"].into_iter().enumerate() {
696            stream.insert(offset as FloatType, note(name, 1.0)?);
697        }
698        stream.shift_elements(1.0, Some(1.0), Some(3.0));
699        assert_eq!(
700            placed(&stream),
701            named(&[("C4", 0.0), ("D4", 2.0), ("E4", 3.0), ("F4", 3.0)])
702        );
703        Ok(())
704    }
705
706    #[test]
707    fn scale_offsets_holds_its_anchor() -> Result<()> {
708        let mut stream = Stream::new();
709        for (offset, name) in ["C4", "D4", "E4"].into_iter().enumerate() {
710            stream.insert(offset as FloatType + 1.0, note(name, 1.0)?);
711        }
712        let high = stream.scale_offsets(2.0, Anchor::Highest, Anchor::Zero)?;
713        assert_eq!(
714            placed(&high),
715            named(&[("C4", -1.0), ("D4", 1.0), ("E4", 3.0)])
716        );
717        let zero = stream.scale_offsets(2.0, Anchor::Zero, Anchor::Zero)?;
718        assert_eq!(
719            placed(&zero),
720            named(&[("C4", 2.0), ("D4", 4.0), ("E4", 6.0)])
721        );
722        Ok(())
723    }
724
725    #[test]
726    fn a_pickup_is_padded_to_its_bar() -> Result<()> {
727        let mut measure = Stream::with_kind(StreamKind::Measure);
728        measure.insert(0.0, TimeSignature::new(3, 4)?);
729        measure.insert(0.0, note("C4", 2.0)?);
730        assert_eq!(measure.bar_duration_proportion(None)?, op_frac(2.0 / 3.0));
731
732        let mut measure = Stream::with_kind(StreamKind::Measure);
733        measure.insert(0.0, TimeSignature::new(3, 4)?);
734        measure.insert(0.0, note("C4", 1.0)?);
735        measure.pad_as_anacrusis(None, false)?;
736        assert_eq!(measure.padding_left(), 2.0);
737        Ok(())
738    }
739
740    #[test]
741    fn without_a_time_signature_the_notes_say_the_bar() -> Result<()> {
742        let mut measure = Stream::with_kind(StreamKind::Measure);
743        measure.insert(0.0, Rest::new(Duration::quarter()));
744        measure.insert(1.0, note("C4", 1.0)?);
745        measure.pad_as_anacrusis(None, true)?;
746        assert_eq!(measure.padding_left(), 0.0);
747        assert_eq!(placed(&measure), named(&[("C4", 0.0)]));
748        assert!(
749            Stream::with_kind(StreamKind::Measure)
750                .bar_duration_proportion(None)
751                .is_err()
752        );
753        Ok(())
754    }
755}