Skip to main content

music21_rs/
spanner.rs

1//! Spanners: marks that join several notes, music21's `spanner` module.
2//!
3//! music21 keeps a spanner as an object holding the very notes it joins. A
4//! stream here owns what it holds, so a spanner names its notes by where they
5//! stand instead: by their positions in [`Stream::leaves`] of the stream the
6//! spanner belongs to, first to last.
7//!
8//! [`Stream::leaves`]: crate::Stream::leaves
9
10use crate::defaults::FloatType;
11use crate::display::Drawn;
12use crate::notation::Placement;
13
14/// Which of music21's spanner classes a spanner is.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum SpannerKind {
18    /// A curve joining notes played in one breath or bow: music21's `Slur`.
19    Slur,
20    /// A hairpin opening out, growing louder: music21's `Crescendo`.
21    Crescendo,
22    /// A hairpin closing, growing softer: music21's `Diminuendo`.
23    Diminuendo,
24    /// An octave line, `8va` or `15mb`: music21's `Ottava`.
25    Ottava,
26    /// A bracket or line over notes, as a *ligatura* or a passage to be
27    /// played together: music21's `Line`.
28    Line,
29    /// How long a pedal is held: music21's `PedalMark`.
30    PedalMark,
31    /// The wavy line carrying a trill on past its note: music21's
32    /// `TrillExtension`.
33    TrillExtension,
34    /// One arpeggio across several chords, as across both hands' staves:
35    /// music21's `ArpeggioMarkSpanner`.
36    ArpeggioMark,
37    /// A glissando or a slide from one note to the next: music21's
38    /// `Glissando`.
39    Glissando,
40    /// A tremolo alternating between two notes: music21's `TremoloSpanner`.
41    TremoloSpanner,
42    /// Notes sung to one syllable as one figure, as chant writes them:
43    /// music21's volpiano `Neume`.
44    Neume,
45}
46
47impl SpannerKind {
48    /// music21's class name for the kind.
49    pub fn class_name(self) -> &'static str {
50        match self {
51            Self::Slur => "Slur",
52            Self::Crescendo => "Crescendo",
53            Self::Diminuendo => "Diminuendo",
54            Self::Ottava => "Ottava",
55            Self::Line => "Line",
56            Self::PedalMark => "PedalMark",
57            Self::ArpeggioMark => "ArpeggioMarkSpanner",
58            Self::TrillExtension => "TrillExtension",
59            Self::Glissando => "Glissando",
60            Self::TremoloSpanner => "TremoloSpanner",
61            Self::Neume => "Neume",
62        }
63    }
64
65    /// The class spanners of this kind are counted with where music21 numbers
66    /// them for a score: a crescendo and a diminuendo are both hairpins,
67    /// `DynamicWedge`, and share their numbers.
68    pub fn numbering_class(self) -> &'static str {
69        match self {
70            Self::Crescendo | Self::Diminuendo => "DynamicWedge",
71            other => other.class_name(),
72        }
73    }
74
75    /// Whether this is a hairpin: music21's `DynamicWedge`.
76    pub fn is_wedge(self) -> bool {
77        matches!(self, Self::Crescendo | Self::Diminuendo)
78    }
79}
80
81/// A mark joining several notes: music21's `Spanner`.
82#[derive(Clone, Debug, PartialEq, Eq)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84#[must_use]
85pub struct Spanner {
86    kind: SpannerKind,
87    /// Each element joined, by position, or nothing for one that stands in
88    /// no stream at all -- which music21 allows, and still counts.
89    spanned: Vec<Option<usize>>,
90    #[cfg_attr(feature = "serde", serde(default))]
91    placement: Drawn<Option<Placement>>,
92    #[cfg_attr(feature = "serde", serde(default))]
93    line_type: Drawn<Option<String>>,
94    #[cfg_attr(feature = "serde", serde(default))]
95    spread: Drawn<Option<FloatType>>,
96    #[cfg_attr(feature = "serde", serde(default))]
97    shift: Option<OctaveShift>,
98    #[cfg_attr(feature = "serde", serde(default))]
99    ends: Drawn<LineEnds>,
100    #[cfg_attr(feature = "serde", serde(default))]
101    pedal: Option<Pedal>,
102    #[cfg_attr(feature = "serde", serde(default))]
103    arpeggio: crate::expressions::ArpeggioType,
104    #[cfg_attr(feature = "serde", serde(default))]
105    glissando: Option<Glissando>,
106    #[cfg_attr(feature = "serde", serde(default))]
107    marks: Option<u8>,
108    #[cfg_attr(feature = "serde", serde(default))]
109    offset: Drawn<FloatType>,
110}
111
112/// How a glissando is played: music21's `Glissando.slideType`.
113#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115pub enum SlideType {
116    /// Through every semitone between, which is how music21's starts out.
117    #[default]
118    Chromatic,
119    /// With no steps at all, a slide or a smear.
120    Continuous,
121    /// Through the notes of the scale, as a harp plays one.
122    Diatonic,
123    /// Over the white keys.
124    White,
125    /// Over the black keys.
126    Black,
127}
128
129impl SlideType {
130    /// music21's name for it: `chromatic`, `continuous`, `diatonic`,
131    /// `white` or `black`.
132    pub fn as_str(self) -> &'static str {
133        match self {
134            Self::Chromatic => "chromatic",
135            Self::Continuous => "continuous",
136            Self::Diatonic => "diatonic",
137            Self::White => "white",
138            Self::Black => "black",
139        }
140    }
141
142    /// Reads music21's name for a slide, whatever its case.
143    ///
144    /// # Errors
145    ///
146    /// A name that is none of the five, as music21's `SpannerException`.
147    pub fn from_name(name: &str) -> crate::error::Result<Self> {
148        let lower = name.to_lowercase();
149        [
150            Self::Chromatic,
151            Self::Continuous,
152            Self::Diatonic,
153            Self::White,
154            Self::Black,
155        ]
156        .into_iter()
157        .find(|kind| kind.as_str() == lower)
158        .ok_or_else(|| crate::error::Error::Value(format!("not a valid value: {name}")))
159    }
160}
161
162/// What a glissando says beside the notes it joins: how it is played and
163/// the words written along it, music21's `slideType` and `label`.
164#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub struct Glissando {
167    /// How it is played.
168    pub slide_type: SlideType,
169    /// The words along the line, *gliss.*, where it has any.
170    pub label: Option<String>,
171}
172
173/// Which of music21's pedal objects a [`PedalObject`] is.
174#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176pub enum PedalObjectKind {
177    /// The pedal let up and put down again at once: music21's
178    /// `PedalBounce`.
179    Bounce,
180    /// Where the pedal line stops being drawn while the pedalling goes on,
181    /// usually beside *simile*: music21's `PedalGapStart`.
182    GapStart,
183    /// Where the pedal line is drawn again: music21's `PedalGapEnd`.
184    GapEnd,
185}
186
187impl PedalObjectKind {
188    /// music21's class name for it.
189    pub fn class_name(self) -> &'static str {
190        match self {
191            Self::Bounce => "PedalBounce",
192            Self::GapStart => "PedalGapStart",
193            Self::GapEnd => "PedalGapEnd",
194        }
195    }
196
197    /// Reads music21's class name for one.
198    pub fn from_class_name(name: &str) -> Option<Self> {
199        [Self::Bounce, Self::GapStart, Self::GapEnd]
200            .into_iter()
201            .find(|kind| kind.class_name() == name)
202    }
203}
204
205/// A moment inside a held pedal -- a bounce, or a gap in its line -- which
206/// stands in the stream at its own offset and is joined by the pedal mark
207/// it belongs to: music21's `PedalObject`.
208#[derive(Clone, Debug, PartialEq, Eq, Hash)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210#[must_use]
211pub struct PedalObject {
212    kind: PedalObjectKind,
213    #[cfg_attr(feature = "serde", serde(default))]
214    placement: Drawn<Option<Placement>>,
215}
216
217impl PedalObject {
218    /// A pedal object of a kind.
219    pub fn new(kind: PedalObjectKind) -> Self {
220        Self {
221            kind,
222            placement: Drawn(None),
223        }
224    }
225
226    /// Which kind it is.
227    pub fn kind(&self) -> PedalObjectKind {
228        self.kind
229    }
230
231    /// Which side of the staff it is drawn on, where the score says.
232    pub fn placement(&self) -> Option<Placement> {
233        self.placement.0
234    }
235
236    /// Says which side of the staff it is drawn on.
237    pub fn set_placement(&mut self, placement: Option<Placement>) {
238        self.placement.0 = placement;
239    }
240}
241
242/// How a line's ends are drawn: music21's `startTick`, `endTick`,
243/// `startHeight` and `endHeight`.
244#[derive(Clone, Copy, Debug, PartialEq, Default)]
245#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
246pub struct LineEnds {
247    /// The tick at the start.
248    pub start: LineEnd,
249    /// The tick at the end.
250    pub end: LineEnd,
251    /// How long the tick at the start is, in tenths of a staff space.
252    pub start_height: Option<FloatType>,
253    /// How long the tick at the end is, in tenths of a staff space.
254    pub end_height: Option<FloatType>,
255}
256
257/// The tick at one end of a line.
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
260pub enum LineEnd {
261    /// A tick up.
262    Up,
263    /// A tick down, which is how music21's lines start out.
264    #[default]
265    Down,
266    /// An arrowhead.
267    Arrow,
268    /// A tick both ways.
269    Both,
270    /// No tick at all.
271    None,
272}
273
274impl LineEnd {
275    /// music21's name for it, which is MusicXML's too: `up`, `down`,
276    /// `arrow`, `both` or `none`.
277    pub fn as_str(self) -> &'static str {
278        match self {
279            Self::Up => "up",
280            Self::Down => "down",
281            Self::Arrow => "arrow",
282            Self::Both => "both",
283            Self::None => "none",
284        }
285    }
286
287    /// Reads music21's name for a tick, whatever its case.
288    pub fn from_name(name: &str) -> crate::error::Result<Self> {
289        Ok(match name.to_lowercase().as_str() {
290            "up" => Self::Up,
291            "down" => Self::Down,
292            "arrow" => Self::Arrow,
293            "both" => Self::Both,
294            "none" => Self::None,
295            other => {
296                return Err(crate::error::Error::Value(format!(
297                    "not a valid value: {other}"
298                )));
299            }
300        })
301    }
302}
303
304/// Which pedal a pedal mark holds down: music21's `PedalType`.
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
306#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
307pub enum PedalType {
308    /// The sustaining pedal.
309    Sustain,
310    /// The sostenuto pedal, which holds only the notes already down.
311    Sostenuto,
312    /// The soft pedal.
313    Soft,
314    /// A practice piano's silent pedal.
315    Silent,
316}
317
318/// How a pedal mark is drawn: music21's `PedalForm`.
319#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321pub enum PedalForm {
322    /// A bracket under the notes held.
323    Line,
324    /// *Ped.* where it goes down and a star where it comes up.
325    Symbol,
326    /// *Ped.* where it goes down and nothing where it comes up.
327    SymbolAlt,
328    /// *Ped.* where it goes down and a line from there.
329    SymbolLine,
330}
331
332/// A pedal mark's pedal and how it is drawn: music21's `pedalType`,
333/// `pedalForm` and `abbreviated`, the first two of which a score need not
334/// say.
335#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
336#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
337pub struct Pedal {
338    /// Which pedal is held.
339    pub pedal_type: Option<PedalType>,
340    /// How the mark is drawn.
341    pub form: Option<PedalForm>,
342    /// Whether the symbol is shortened, *P.* for *Ped.*
343    pub abbreviated: bool,
344}
345
346impl PedalType {
347    /// music21's name for it: `sustain`, `sostenuto`, `soft` or `silent`.
348    pub fn as_str(self) -> &'static str {
349        match self {
350            Self::Sustain => "sustain",
351            Self::Sostenuto => "sostenuto",
352            Self::Soft => "soft",
353            Self::Silent => "silent",
354        }
355    }
356
357    /// Reads music21's name for a pedal.
358    pub fn from_name(name: &str) -> Option<Self> {
359        [Self::Sustain, Self::Sostenuto, Self::Soft, Self::Silent]
360            .into_iter()
361            .find(|kind| kind.as_str() == name)
362    }
363}
364
365impl PedalForm {
366    /// music21's name for it: `line`, `symbol`, `symbolalt` or
367    /// `symbolline`.
368    pub fn as_str(self) -> &'static str {
369        match self {
370            Self::Line => "line",
371            Self::Symbol => "symbol",
372            Self::SymbolAlt => "symbolalt",
373            Self::SymbolLine => "symbolline",
374        }
375    }
376
377    /// Reads music21's name for a form.
378    pub fn from_name(name: &str) -> Option<Self> {
379        [Self::Line, Self::Symbol, Self::SymbolAlt, Self::SymbolLine]
380            .into_iter()
381            .find(|form| form.as_str() == name)
382    }
383
384    /// Whether a line is drawn from where the pedal goes down.
385    pub fn has_line(self) -> bool {
386        matches!(self, Self::Line | Self::SymbolLine)
387    }
388}
389
390/// How far and which way an octave line moves the notes under it:
391/// music21's `Ottava.type`.
392#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
393#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
394pub struct OctaveShift {
395    size: u8,
396    up: bool,
397    transposing: bool,
398}
399
400impl OctaveShift {
401    /// A shift by music21's name for it: `8va`, `8vb`, `15ma`, `15mb`,
402    /// `22da` or `22db`. `transposing` says whether the notes under it are
403    /// written where they sound, as music21's `transposing` says.
404    pub fn from_name(name: &str, transposing: bool) -> crate::error::Result<Self> {
405        let (size, up) = match name.to_lowercase().as_str() {
406            "8va" => (8, true),
407            "8vb" => (8, false),
408            "15ma" => (15, true),
409            "15mb" => (15, false),
410            "22da" => (22, true),
411            "22db" => (22, false),
412            other => {
413                return Err(crate::error::Error::Value(format!(
414                    "cannot create Ottava of type: {other}"
415                )));
416            }
417        };
418        Ok(Self {
419            size,
420            up,
421            transposing,
422        })
423    }
424
425    /// music21's name for the shift, `8va`.
426    pub fn name(&self) -> &'static str {
427        match (self.size, self.up) {
428            (8, true) => "8va",
429            (8, false) => "8vb",
430            (15, true) => "15ma",
431            (15, false) => "15mb",
432            (_, true) => "22da",
433            (_, false) => "22db",
434        }
435    }
436
437    /// 8, 15 or 22: music21's `shiftMagnitude`.
438    pub fn magnitude(&self) -> u8 {
439        self.size
440    }
441
442    /// Whether the notes sound higher than written: an `8va` does.
443    pub fn up(&self) -> bool {
444        self.up
445    }
446
447    /// Whether the notes under the line are written where they sound:
448    /// music21's `transposing`.
449    pub fn transposing(&self) -> bool {
450        self.transposing
451    }
452
453    /// Says whether the notes under the line are written where they sound.
454    /// This moves no notes; [`crate::Stream::to_sounding_pitch`] and
455    /// [`crate::Stream::to_written_pitch`] move them and say so.
456    pub fn set_transposing(&mut self, transposing: bool) {
457        self.transposing = transposing;
458    }
459
460    /// How far the notes under the line sound from where they are written,
461    /// one, two or three octaves up or down: music21's `interval()`.
462    pub fn interval(&self) -> crate::interval::Interval {
463        let name = if self.up {
464            format!("P{}", self.size)
465        } else {
466            format!("P-{}", self.size)
467        };
468        crate::interval::Interval::from_name(&name)
469            .expect("a perfect octave, fifteenth or twenty-second is an interval")
470    }
471}
472
473impl Spanner {
474    /// A spanner of a kind joining the elements at these positions.
475    pub fn new(kind: SpannerKind, spanned: Vec<usize>) -> Self {
476        Self::with_unplaced(kind, spanned.into_iter().map(Some).collect())
477    }
478
479    /// A spanner of a kind joining elements some of which stand nowhere: a
480    /// score read from a file may leave a spanner holding a note no stream
481    /// holds. It still counts that note among its own, so the ends and the
482    /// length are those of the whole list.
483    pub fn with_unplaced(kind: SpannerKind, spanned: Vec<Option<usize>>) -> Self {
484        Self {
485            kind,
486            spanned,
487            placement: Drawn(None),
488            line_type: Drawn(None),
489            spread: Drawn(None),
490            shift: None,
491            ends: Drawn(LineEnds::default()),
492            pedal: None,
493            arpeggio: crate::expressions::ArpeggioType::Normal,
494            glissando: None,
495            marks: None,
496            offset: Drawn(0.0),
497        }
498    }
499
500    /// A glissando over the elements at these positions, drawn as a wavy
501    /// line and played chromatically, as music21's starts out.
502    pub fn glissando(spanned: Vec<usize>) -> Self {
503        let mut glissando = Self::new(SpannerKind::Glissando, spanned);
504        glissando.line_type = Drawn(Some("wavy".to_string()));
505        glissando.glissando = Some(Glissando::default());
506        glissando
507    }
508
509    /// How a glissando is played and what is written along it.
510    pub fn glissando_details(&self) -> Option<&Glissando> {
511        self.glissando.as_ref()
512    }
513
514    /// Says how a glissando is played and what is written along it.
515    pub fn set_glissando_details(&mut self, glissando: Option<Glissando>) {
516        self.glissando = glissando;
517    }
518
519    /// A tremolo alternating between the elements at these positions, with
520    /// so many strokes through the stems.
521    ///
522    /// # Errors
523    ///
524    /// More than eight strokes, as music21's `TremoloException`.
525    pub fn tremolo(marks: u8, spanned: Vec<usize>) -> crate::error::Result<Self> {
526        let mut tremolo = Self::new(SpannerKind::TremoloSpanner, spanned);
527        tremolo.set_number_of_marks(Some(marks))?;
528        Ok(tremolo)
529    }
530
531    /// How many strokes a tremolo between notes is written with: music21's
532    /// `numberOfMarks`.
533    pub fn number_of_marks(&self) -> Option<u8> {
534        self.marks
535    }
536
537    /// Says how many strokes a tremolo between notes is written with.
538    ///
539    /// # Errors
540    ///
541    /// More than eight, as music21's `TremoloException`.
542    pub fn set_number_of_marks(&mut self, marks: Option<u8>) -> crate::error::Result<()> {
543        if marks.is_some_and(|marks| marks > 8) {
544            return Err(crate::error::Error::Value(
545                "Number of marks must be a number from 0 to 8".to_string(),
546            ));
547        }
548        self.marks = marks;
549        Ok(())
550    }
551
552    /// Where the spanner itself stands in the stream holding it: nought for
553    /// one a score is read with. A pedal mark drawn as a sign and a line
554    /// says where its line resumes by it.
555    pub fn offset(&self) -> FloatType {
556        self.offset.0
557    }
558
559    /// Says where the spanner itself stands in the stream holding it.
560    pub fn set_offset(&mut self, offset: FloatType) {
561        self.offset.0 = offset;
562    }
563
564    /// One arpeggio across the chords at these positions.
565    pub fn arpeggio_mark(arpeggio: crate::expressions::ArpeggioType, spanned: Vec<usize>) -> Self {
566        let mut mark = Self::new(SpannerKind::ArpeggioMark, spanned);
567        mark.arpeggio = arpeggio;
568        mark
569    }
570
571    /// Which way an arpeggio across several chords is spread.
572    pub fn arpeggio(&self) -> crate::expressions::ArpeggioType {
573        self.arpeggio
574    }
575
576    /// Says which way an arpeggio across several chords is spread.
577    pub fn set_arpeggio(&mut self, arpeggio: crate::expressions::ArpeggioType) {
578        self.arpeggio = arpeggio;
579    }
580
581    /// A line over the elements at these positions, solid with a tick down
582    /// at each end, as music21's starts out.
583    pub fn line(spanned: Vec<usize>) -> Self {
584        let mut line = Self::new(SpannerKind::Line, spanned);
585        line.line_type = Drawn(Some("solid".to_string()));
586        line
587    }
588
589    /// How a line's ends are drawn.
590    pub fn ends(&self) -> LineEnds {
591        self.ends.0
592    }
593
594    /// Says how a line's ends are drawn.
595    pub fn set_ends(&mut self, ends: LineEnds) {
596        self.ends.0 = ends;
597    }
598
599    /// A pedal held under the elements at these positions.
600    pub fn pedal_mark(pedal: Pedal, spanned: Vec<usize>) -> Self {
601        let mut mark = Self::new(SpannerKind::PedalMark, spanned);
602        mark.pedal = Some(pedal);
603        mark
604    }
605
606    /// Which pedal a pedal mark holds and how it is drawn.
607    pub fn pedal(&self) -> Option<Pedal> {
608        self.pedal
609    }
610
611    /// Says which pedal a pedal mark holds and how it is drawn.
612    pub fn set_pedal(&mut self, pedal: Option<Pedal>) {
613        self.pedal = pedal;
614    }
615
616    /// An octave line over the elements at these positions, drawn above them
617    /// as music21's starts out.
618    pub fn ottava(shift: OctaveShift, spanned: Vec<usize>) -> Self {
619        let mut ottava = Self::new(SpannerKind::Ottava, spanned);
620        ottava.placement = Drawn(Some(Placement::Above));
621        ottava.shift = Some(shift);
622        ottava
623    }
624
625    /// How far and which way an octave line moves its notes.
626    pub fn shift(&self) -> Option<OctaveShift> {
627        self.shift
628    }
629
630    /// Says how far and which way an octave line moves its notes.
631    pub fn set_shift(&mut self, shift: Option<OctaveShift>) {
632        self.shift = shift;
633    }
634
635    /// A slur over the elements at these positions.
636    pub fn slur(spanned: Vec<usize>) -> Self {
637        Self::new(SpannerKind::Slur, spanned)
638    }
639
640    /// A hairpin over the elements at these positions, drawn below them and
641    /// opening fifteen tenths wide, as music21's starts out.
642    pub fn wedge(kind: SpannerKind, spanned: Vec<usize>) -> Self {
643        let mut wedge = Self::new(kind, spanned);
644        wedge.placement = Drawn(Some(Placement::Below));
645        wedge.spread = Drawn(Some(15.0));
646        wedge
647    }
648
649    /// How wide a hairpin opens, in tenths of a staff space: music21's
650    /// `spread`.
651    pub fn spread(&self) -> Option<FloatType> {
652        self.spread.0
653    }
654
655    /// Says how wide a hairpin opens.
656    pub fn set_spread(&mut self, spread: Option<FloatType>) {
657        self.spread.0 = spread;
658    }
659
660    /// Which kind of spanner this is.
661    pub fn kind(&self) -> SpannerKind {
662        self.kind
663    }
664
665    /// The positions of the elements it joins, first to last, nothing for
666    /// one that stands in no stream.
667    pub fn spanned(&self) -> &[Option<usize>] {
668        &self.spanned
669    }
670
671    /// The positions of the elements it joins, to be moved where an edit
672    /// has moved them.
673    pub(crate) fn spanned_mut(&mut self) -> &mut Vec<Option<usize>> {
674        &mut self.spanned
675    }
676
677    /// Moves each place it names to where `moved` says that element now
678    /// stands, indexed by the old place: nothing for one no longer held.
679    pub(crate) fn move_places(&mut self, moved: &[Option<usize>]) {
680        for place in &mut self.spanned {
681            *place = place.and_then(|old| moved.get(old).copied().flatten());
682        }
683    }
684
685    /// Joins an element once where moving its places has made it join one
686    /// twice, keeping the first.
687    pub(crate) fn drop_repeated_places(&mut self) {
688        let mut seen: Vec<usize> = Vec::new();
689        self.spanned.retain(|place| match place {
690            Some(place) if seen.contains(place) => false,
691            Some(place) => {
692                seen.push(*place);
693                true
694            }
695            None => true,
696        });
697    }
698
699    /// How many elements it joins, those standing nowhere included.
700    pub fn len(&self) -> usize {
701        self.spanned.len()
702    }
703
704    /// Whether it joins nothing at all.
705    pub fn is_empty(&self) -> bool {
706        self.spanned.is_empty()
707    }
708
709    /// Whether the element at a position is the first it joins: music21's
710    /// `isFirst`.
711    pub fn is_first(&self, position: usize) -> bool {
712        self.spanned.first() == Some(&Some(position))
713    }
714
715    /// Whether the element at a position is the last it joins: music21's
716    /// `isLast`.
717    pub fn is_last(&self, position: usize) -> bool {
718        self.spanned.last() == Some(&Some(position))
719    }
720
721    /// Which side of the notes it is drawn on, where the score says.
722    pub fn placement(&self) -> Option<Placement> {
723        self.placement.0
724    }
725
726    /// Says which side of the notes it is drawn on.
727    pub fn set_placement(&mut self, placement: Option<Placement>) {
728        self.placement.0 = placement;
729    }
730
731    /// How its line is drawn where that is not solid, `dashed` or `dotted`:
732    /// music21's `style.lineType`.
733    pub fn line_type(&self) -> Option<&str> {
734        self.line_type.0.as_deref()
735    }
736
737    /// Says how its line is drawn.
738    pub fn set_line_type(&mut self, line_type: Option<String>) {
739        self.line_type.0 = line_type;
740    }
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746
747    #[test]
748    fn a_spanner_knows_its_ends() {
749        let slur = Spanner::slur(vec![3, 4, 5]);
750        assert!(slur.is_first(3));
751        assert!(slur.is_last(5));
752        assert!(!slur.is_first(4) && !slur.is_last(4));
753        assert_eq!(slur.kind().class_name(), "Slur");
754    }
755}