1use std::fmt;
11use std::str::FromStr;
12
13use crate::{
14 defaults::IntegerType,
15 duration::DurationType,
16 error::{Error, Result},
17};
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum TieType {
23 Start,
25 Stop,
27 Continue,
29 LetRing,
31 ContinueLetRing,
33}
34
35impl TieType {
36 pub const ALL: [TieType; 5] = [
38 TieType::Start,
39 TieType::Stop,
40 TieType::Continue,
41 TieType::LetRing,
42 TieType::ContinueLetRing,
43 ];
44
45 pub fn as_str(self) -> &'static str {
47 match self {
48 TieType::Start => "start",
49 TieType::Stop => "stop",
50 TieType::Continue => "continue",
51 TieType::LetRing => "let-ring",
52 TieType::ContinueLetRing => "continue-let-ring",
53 }
54 }
55
56 pub fn from_name(name: &str) -> Result<Self> {
58 Self::ALL
59 .into_iter()
60 .find(|candidate| candidate.as_str() == name)
61 .ok_or_else(|| {
62 let valid = Self::ALL
63 .iter()
64 .map(|candidate| format!("'{}'", candidate.as_str()))
65 .collect::<Vec<_>>()
66 .join(", ");
67 Error::Notation(format!("Type must be one of ({valid}), not {name}"))
68 })
69 }
70}
71
72impl fmt::Display for TieType {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.write_str(self.as_str())
75 }
76}
77
78#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub enum TieStyle {
82 #[default]
84 Normal,
85 Dotted,
87 Dashed,
89 Hidden,
91}
92
93impl TieStyle {
94 pub const ALL: [TieStyle; 4] = [
96 TieStyle::Normal,
97 TieStyle::Dotted,
98 TieStyle::Dashed,
99 TieStyle::Hidden,
100 ];
101
102 pub fn as_str(self) -> &'static str {
104 match self {
105 TieStyle::Normal => "normal",
106 TieStyle::Dotted => "dotted",
107 TieStyle::Dashed => "dashed",
108 TieStyle::Hidden => "hidden",
109 }
110 }
111
112 pub fn from_name(name: &str) -> Result<Self> {
114 Self::ALL
115 .into_iter()
116 .find(|candidate| candidate.as_str() == name)
117 .ok_or_else(|| Error::Notation(format!("not a valid tie style: {name}")))
118 }
119}
120
121impl fmt::Display for TieStyle {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.write_str(self.as_str())
124 }
125}
126
127#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130pub enum Placement {
131 Above,
133 Below,
135}
136
137impl Placement {
138 pub fn as_str(self) -> &'static str {
140 match self {
141 Placement::Above => "above",
142 Placement::Below => "below",
143 }
144 }
145
146 pub fn from_name(name: &str) -> Result<Self> {
148 match name {
149 "above" => Ok(Placement::Above),
150 "below" => Ok(Placement::Below),
151 other => Err(Error::Notation(format!("not a valid placement: {other}"))),
152 }
153 }
154}
155
156impl fmt::Display for Placement {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str(self.as_str())
159 }
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, Hash)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169#[must_use]
170pub struct Tie {
171 tie_type: TieType,
172 style: TieStyle,
173 placement: Option<Placement>,
174}
175
176impl Tie {
177 pub fn new(tie_type: TieType) -> Self {
179 Self {
180 tie_type,
181 style: TieStyle::default(),
182 placement: None,
183 }
184 }
185
186 pub fn from_name(name: &str) -> Result<Self> {
189 Ok(Self::new(TieType::from_name(name)?))
190 }
191
192 pub fn tie_type(&self) -> TieType {
194 self.tie_type
195 }
196
197 pub fn set_tie_type(&mut self, tie_type: TieType) {
199 self.tie_type = tie_type;
200 }
201
202 pub fn style(&self) -> TieStyle {
204 self.style
205 }
206
207 pub fn set_style(&mut self, style: TieStyle) {
209 self.style = style;
210 }
211
212 pub fn placement(&self) -> Option<Placement> {
214 self.placement
215 }
216
217 pub fn set_placement(&mut self, placement: Option<Placement>) {
219 self.placement = placement;
220 }
221}
222
223impl Default for Tie {
224 fn default() -> Self {
226 Self::new(TieType::Start)
227 }
228}
229
230impl fmt::Display for Tie {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 write!(f, "Tie {}", self.tie_type)
233 }
234}
235
236impl FromStr for Tie {
237 type Err = Error;
238
239 fn from_str(name: &str) -> Result<Self> {
240 Self::from_name(name)
241 }
242}
243
244#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247#[must_use]
248pub enum Notehead {
249 ArrowDown,
251 ArrowUp,
253 BackSlashed,
255 CircleDot,
257 CircleX,
259 Circled,
261 Cluster,
263 Cross,
265 Diamond,
267 Do,
269 Fa,
271 FaUp,
273 InvertedTriangle,
275 La,
277 LeftTriangle,
279 Mi,
281 NoneShape,
283 #[default]
285 Normal,
286 Other,
288 Re,
290 Rectangle,
292 Slash,
294 Slashed,
296 So,
298 Square,
300 Ti,
302 Triangle,
304 X,
306}
307
308impl Notehead {
309 pub const ALL: [Notehead; 28] = [
311 Notehead::ArrowDown,
312 Notehead::ArrowUp,
313 Notehead::BackSlashed,
314 Notehead::CircleDot,
315 Notehead::CircleX,
316 Notehead::Circled,
317 Notehead::Cluster,
318 Notehead::Cross,
319 Notehead::Diamond,
320 Notehead::Do,
321 Notehead::Fa,
322 Notehead::FaUp,
323 Notehead::InvertedTriangle,
324 Notehead::La,
325 Notehead::LeftTriangle,
326 Notehead::Mi,
327 Notehead::NoneShape,
328 Notehead::Normal,
329 Notehead::Other,
330 Notehead::Re,
331 Notehead::Rectangle,
332 Notehead::Slash,
333 Notehead::Slashed,
334 Notehead::So,
335 Notehead::Square,
336 Notehead::Ti,
337 Notehead::Triangle,
338 Notehead::X,
339 ];
340
341 pub fn as_str(self) -> &'static str {
343 match self {
344 Notehead::ArrowDown => "arrow down",
345 Notehead::ArrowUp => "arrow up",
346 Notehead::BackSlashed => "back slashed",
347 Notehead::CircleDot => "circle dot",
348 Notehead::CircleX => "circle-x",
349 Notehead::Circled => "circled",
350 Notehead::Cluster => "cluster",
351 Notehead::Cross => "cross",
352 Notehead::Diamond => "diamond",
353 Notehead::Do => "do",
354 Notehead::Fa => "fa",
355 Notehead::FaUp => "fa up",
356 Notehead::InvertedTriangle => "inverted triangle",
357 Notehead::La => "la",
358 Notehead::LeftTriangle => "left triangle",
359 Notehead::Mi => "mi",
360 Notehead::NoneShape => "none",
361 Notehead::Normal => "normal",
362 Notehead::Other => "other",
363 Notehead::Re => "re",
364 Notehead::Rectangle => "rectangle",
365 Notehead::Slash => "slash",
366 Notehead::Slashed => "slashed",
367 Notehead::So => "so",
368 Notehead::Square => "square",
369 Notehead::Ti => "ti",
370 Notehead::Triangle => "triangle",
371 Notehead::X => "x",
372 }
373 }
374
375 pub fn from_name(name: &str) -> Result<Self> {
377 Self::ALL
378 .into_iter()
379 .find(|candidate| candidate.as_str() == name)
380 .ok_or_else(|| Error::Notation(format!("not a valid notehead type name: '{name}'")))
381 }
382}
383
384impl fmt::Display for Notehead {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 f.write_str(self.as_str())
387 }
388}
389
390#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400pub enum BeamType {
401 #[default]
403 Start,
404 Continue,
406 Stop,
408 PartialBeam,
410}
411
412impl BeamType {
413 pub fn as_str(self) -> &'static str {
415 match self {
416 Self::Start => "start",
417 Self::Continue => "continue",
418 Self::Stop => "stop",
419 Self::PartialBeam => "partial",
420 }
421 }
422
423 pub fn from_music21_name(name: &str) -> Option<Self> {
425 match name {
426 "start" => Some(Self::Start),
427 "continue" => Some(Self::Continue),
428 "stop" => Some(Self::Stop),
429 "partial" => Some(Self::PartialBeam),
430 _ => None,
431 }
432 }
433}
434
435impl fmt::Display for BeamType {
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 f.write_str(self.as_str())
438 }
439}
440
441#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
443#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
444pub enum BeamDirection {
445 Left,
447 Right,
449}
450
451impl BeamDirection {
452 pub fn as_str(self) -> &'static str {
454 match self {
455 Self::Left => "left",
456 Self::Right => "right",
457 }
458 }
459
460 pub fn from_music21_name(name: &str) -> Option<Self> {
462 match name {
463 "left" => Some(Self::Left),
464 "right" => Some(Self::Right),
465 _ => None,
466 }
467 }
468}
469
470impl fmt::Display for BeamDirection {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 f.write_str(self.as_str())
473 }
474}
475
476#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
478#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
479#[must_use]
480pub struct Beam {
481 beam_type: Option<BeamType>,
485 direction: Option<BeamDirection>,
486 number: Option<u32>,
487}
488
489impl Beam {
490 pub fn new(beam_type: impl Into<Option<BeamType>>, direction: Option<BeamDirection>) -> Self {
492 Self {
493 beam_type: beam_type.into(),
494 direction,
495 number: None,
496 }
497 }
498
499 pub fn beam_type(&self) -> Option<BeamType> {
502 self.beam_type
503 }
504
505 pub fn set_beam_type(&mut self, beam_type: Option<BeamType>) {
507 self.beam_type = beam_type;
508 }
509
510 pub fn direction(&self) -> Option<BeamDirection> {
512 self.direction
513 }
514
515 pub fn set_direction(&mut self, direction: Option<BeamDirection>) {
517 self.direction = direction;
518 }
519
520 pub fn number(&self) -> Option<u32> {
522 self.number
523 }
524
525 pub fn set_number(&mut self, number: Option<u32>) {
527 self.number = number;
528 }
529}
530
531impl fmt::Display for Beam {
532 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 let number = match self.number {
536 Some(number) => number.to_string(),
537 None => "None".to_string(),
538 };
539 let beam_type = match self.beam_type {
540 Some(beam_type) => beam_type.to_string(),
541 None => "None".to_string(),
542 };
543 match self.direction {
544 Some(direction) => write!(f, "{number}/{beam_type}/{direction}"),
545 None => write!(f, "{number}/{beam_type}"),
546 }
547 }
548}
549
550#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
553#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
554#[must_use]
555pub struct Beams {
556 beams: Vec<Beam>,
557 feathered: bool,
559}
560
561const BEAMABLE: [(DurationType, u32); 9] = [
564 (DurationType::Eighth, 1),
565 (DurationType::Sixteenth, 2),
566 (DurationType::ThirtySecond, 3),
567 (DurationType::SixtyFourth, 4),
568 (DurationType::HundredTwentyEighth, 5),
569 (DurationType::TwoHundredFiftySixth, 6),
570 (DurationType::FiveHundredTwelfth, 7),
571 (DurationType::TenTwentyFourth, 8),
572 (DurationType::TwentyFortyEighth, 9),
573];
574
575impl Beams {
576 pub fn new() -> Self {
578 Self::default()
579 }
580
581 pub fn beams(&self) -> &[Beam] {
583 &self.beams
584 }
585
586 pub fn len(&self) -> usize {
588 self.beams.len()
589 }
590
591 pub fn is_empty(&self) -> bool {
593 self.beams.is_empty()
594 }
595
596 pub fn feathered(&self) -> bool {
598 self.feathered
599 }
600
601 pub fn set_feathered(&mut self, feathered: bool) {
603 self.feathered = feathered;
604 }
605
606 pub fn beams_mut(&mut self) -> &mut Vec<Beam> {
608 &mut self.beams
609 }
610
611 pub fn set_beams(&mut self, beams: Vec<Beam>) {
613 self.beams = beams;
614 }
615
616 pub fn append(
618 &mut self,
619 beam_type: impl Into<Option<BeamType>>,
620 direction: Option<BeamDirection>,
621 ) {
622 let mut beam = Beam::new(beam_type, direction);
623 beam.set_number(Some(self.beams.len() as u32 + 1));
624 self.beams.push(beam);
625 }
626
627 pub const LEVELS: usize = BEAMABLE.len();
632
633 pub fn levels_for(duration_type: DurationType) -> Option<u32> {
635 BEAMABLE
636 .into_iter()
637 .find(|(candidate, _)| *candidate == duration_type)
638 .map(|(_, levels)| levels)
639 }
640
641 pub fn fill(&mut self, duration_type: DurationType, beam_type: Option<BeamType>) -> Result<()> {
647 let Some(levels) = Self::levels_for(duration_type) else {
648 return Err(Error::Notation(format!(
649 "cannot fill beams for a {} note",
650 duration_type.music21_name()
651 )));
652 };
653 self.fill_levels(levels, beam_type)
654 }
655
656 pub fn fill_levels(&mut self, levels: u32, beam_type: Option<BeamType>) -> Result<()> {
662 if levels == 0 || levels > BEAMABLE.len() as u32 {
663 return Err(Error::Notation(format!(
664 "cannot fill beams for level {levels}"
665 )));
666 }
667 self.beams.clear();
668 for _ in 0..levels {
669 self.append(None, None);
670 }
671 if let Some(beam_type) = beam_type {
672 self.set_all(beam_type, None);
673 }
674 Ok(())
675 }
676
677 pub fn set_all(&mut self, beam_type: BeamType, direction: Option<BeamDirection>) {
679 for beam in &mut self.beams {
680 beam.beam_type = Some(beam_type);
681 beam.direction = direction;
682 }
683 }
684
685 pub fn by_number(&self, number: u32) -> Option<&Beam> {
687 self.beams.iter().find(|beam| beam.number == Some(number))
688 }
689
690 pub fn set_by_number(
692 &mut self,
693 number: u32,
694 beam_type: BeamType,
695 direction: Option<BeamDirection>,
696 ) -> Result<()> {
697 let Some(beam) = self
698 .beams
699 .iter_mut()
700 .find(|beam| beam.number == Some(number))
701 else {
702 return Err(Error::Notation(format!(
703 "beam number {number} does not exist"
704 )));
705 };
706 beam.beam_type = Some(beam_type);
707 beam.direction = direction;
708 Ok(())
709 }
710
711 pub fn types(&self) -> Vec<Option<BeamType>> {
713 self.beams.iter().map(Beam::beam_type).collect()
714 }
715
716 pub fn numbers(&self) -> Vec<Option<u32>> {
718 self.beams.iter().map(Beam::number).collect()
719 }
720}
721
722impl fmt::Display for Beams {
723 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
725 let written: Vec<String> = self
726 .beams
727 .iter()
728 .map(|beam| format!("<music21.beam.Beam {beam}>"))
729 .collect();
730 f.write_str(&written.join("/"))
731 }
732}
733
734pub fn remove_sandwiched_unbeamables(beams: &mut [Option<Beams>]) {
740 let mut previous: Option<Beams> = None;
741 for index in 0..beams.len() {
742 let next = beams.get(index + 1).cloned().flatten();
743 if previous.is_none() && next.is_none() {
744 beams[index] = None;
745 }
746 previous = beams[index].clone();
747 }
748}
749
750pub fn sanitize_partial_beams(beams: &mut [Option<Beams>]) {
753 for entry in beams.iter_mut() {
754 let Some(group) = entry else {
755 continue;
756 };
757 let joined = group.types().into_iter().flatten().any(|beam_type| {
758 matches!(
759 beam_type,
760 BeamType::Start | BeamType::Stop | BeamType::Continue
761 )
762 });
763 if !joined {
764 *entry = None;
765 continue;
766 }
767 let mut after_start = false;
768 let mut after_stop = false;
769 for beam in group.beams_mut() {
770 match beam.beam_type() {
771 Some(BeamType::Start) => after_start = true,
772 Some(BeamType::Stop) => after_stop = true,
773 Some(BeamType::PartialBeam) => {
774 if after_start && beam.direction() == Some(BeamDirection::Left) {
775 beam.set_direction(Some(BeamDirection::Right));
776 } else if after_stop && beam.direction() == Some(BeamDirection::Right) {
777 beam.set_direction(Some(BeamDirection::Left));
778 }
779 }
780 _ => {}
781 }
782 }
783 }
784}
785
786pub fn merge_connecting_partial_beams(beams: &mut [Option<Beams>]) {
789 for index in 0..beams.len().saturating_sub(1) {
790 let Some(numbers) = beams[index]
791 .as_ref()
792 .map(|group| group.numbers().into_iter().flatten().collect::<Vec<_>>())
793 else {
794 continue;
795 };
796 if beams[index + 1].is_none() {
797 continue;
798 }
799 for number in numbers {
800 let this = beams[index]
801 .as_ref()
802 .and_then(|group| group.by_number(number))
803 .copied();
804 let next = beams[index + 1]
805 .as_ref()
806 .and_then(|group| group.by_number(number))
807 .copied();
808 let (Some(this), Some(next)) = (this, next) else {
809 continue;
810 };
811 if this.beam_type() != Some(BeamType::PartialBeam)
812 || this.direction() != Some(BeamDirection::Right)
813 {
814 continue;
815 }
816 if next.beam_type() == Some(BeamType::PartialBeam)
817 && next.direction() == Some(BeamDirection::Right)
818 {
819 continue;
820 }
821 if matches!(
824 next.beam_type(),
825 Some(BeamType::Continue) | Some(BeamType::Stop)
826 ) {
827 continue;
828 }
829 set_beam(&mut beams[index], number, BeamType::Start, None);
830 let merged = match next.beam_type() {
831 Some(BeamType::PartialBeam) => BeamType::Stop,
832 Some(BeamType::Start) => BeamType::Continue,
833 other => other.unwrap_or(BeamType::Start),
834 };
835 set_beam(&mut beams[index + 1], number, merged, None);
836 }
837 }
838
839 for index in 1..beams.len() {
841 let Some(numbers) = beams[index]
842 .as_ref()
843 .map(|group| group.numbers().into_iter().flatten().collect::<Vec<_>>())
844 else {
845 continue;
846 };
847 if beams[index - 1].is_none() {
848 continue;
849 }
850 for number in numbers {
851 let this = beams[index]
852 .as_ref()
853 .and_then(|group| group.by_number(number))
854 .copied();
855 let previous = beams[index - 1]
856 .as_ref()
857 .and_then(|group| group.by_number(number))
858 .copied();
859 let (Some(this), Some(previous)) = (this, previous) else {
860 continue;
861 };
862 if this.beam_type() != Some(BeamType::PartialBeam)
863 || this.direction() != Some(BeamDirection::Left)
864 || previous.beam_type() != Some(BeamType::Stop)
865 {
866 continue;
867 }
868 set_beam(&mut beams[index], number, BeamType::Stop, None);
869 set_beam(&mut beams[index - 1], number, BeamType::Continue, None);
870 }
871 }
872}
873
874fn set_beam(
876 group: &mut Option<Beams>,
877 number: u32,
878 beam_type: BeamType,
879 direction: Option<BeamDirection>,
880) {
881 if let Some(group) = group {
882 let _ = group.set_by_number(number, beam_type, direction);
883 }
884}
885
886#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
888#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
889pub enum StemDirection {
890 Double,
892 Down,
894 NoStem,
896 #[default]
898 Unspecified,
899 Up,
901}
902
903impl StemDirection {
904 pub const ALL: [StemDirection; 5] = [
910 StemDirection::Double,
911 StemDirection::Down,
912 StemDirection::NoStem,
913 StemDirection::Unspecified,
914 StemDirection::Up,
915 ];
916
917 pub const NAMES: [&'static str; 6] = ["double", "down", "noStem", "none", "unspecified", "up"];
920
921 pub fn as_str(self) -> &'static str {
923 match self {
924 StemDirection::Double => "double",
925 StemDirection::Down => "down",
926 StemDirection::NoStem => "noStem",
927 StemDirection::Unspecified => "unspecified",
928 StemDirection::Up => "up",
929 }
930 }
931
932 pub fn from_name(name: &str) -> Result<Self> {
937 if name == "none" {
938 return Ok(StemDirection::NoStem);
939 }
940 Self::ALL
941 .into_iter()
942 .find(|candidate| candidate.as_str() == name)
943 .ok_or_else(|| Error::Notation(format!("not a valid stem direction name: {name}")))
944 }
945}
946
947impl fmt::Display for StemDirection {
948 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
949 f.write_str(self.as_str())
950 }
951}
952
953#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
955#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
956pub enum Syllabic {
957 Single,
959 Begin,
961 Middle,
963 End,
965 Composite,
967}
968
969impl Syllabic {
970 pub const ALL: [Syllabic; 5] = [
972 Syllabic::Single,
973 Syllabic::Begin,
974 Syllabic::Middle,
975 Syllabic::End,
976 Syllabic::Composite,
977 ];
978
979 pub fn as_str(self) -> &'static str {
981 match self {
982 Syllabic::Single => "single",
983 Syllabic::Begin => "begin",
984 Syllabic::Middle => "middle",
985 Syllabic::End => "end",
986 Syllabic::Composite => "composite",
987 }
988 }
989
990 pub fn from_name(name: &str) -> Result<Self> {
992 Self::ALL
993 .into_iter()
994 .find(|candidate| candidate.as_str() == name)
995 .ok_or_else(|| {
996 Error::Notation(format!(
999 "Syllabic value '{name}' is not in note.SYLLABIC_CHOICES, namely: [None, 'begin', 'single', 'end', 'middle', 'composite']"
1000 ))
1001 })
1002 }
1003}
1004
1005impl fmt::Display for Syllabic {
1006 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1007 f.write_str(self.as_str())
1008 }
1009}
1010
1011#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1017#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1018#[must_use]
1019pub struct Lyric {
1020 text: Option<String>,
1024 number: IntegerType,
1025 syllabic: Syllabic,
1026 said_syllabic: bool,
1032 identifier: Option<String>,
1033 components: Vec<Lyric>,
1034 elision_before: String,
1035}
1036
1037const DEFAULT_ELISION: &str = " ";
1040
1041impl Lyric {
1042 pub fn new(text: impl Into<String>) -> Self {
1044 let mut lyric = Self::unsung();
1045 lyric.text = Some(text.into());
1046 lyric.said_syllabic = true;
1047 lyric
1048 }
1049
1050 pub fn unsung() -> Self {
1054 Self {
1055 text: Some(String::new()),
1056 number: 1,
1057 syllabic: Syllabic::Single,
1058 said_syllabic: false,
1059 identifier: None,
1060 components: Vec::new(),
1061 elision_before: DEFAULT_ELISION.to_string(),
1062 }
1063 }
1064
1065 pub fn from_raw_text(raw_text: &str) -> Self {
1068 let mut lyric = Self::unsung();
1069 lyric.set_raw_text(raw_text);
1070 lyric
1071 }
1072
1073 pub fn text(&self) -> String {
1078 let Some((first, rest)) = self.components.split_first() else {
1079 return self.text.clone().unwrap_or_default();
1080 };
1081 let mut text = first.text();
1082 for component in rest {
1083 text.push_str(&component.elision_before);
1084 text.push_str(&component.text());
1085 }
1086 text
1087 }
1088
1089 pub fn set_text(&mut self, text: impl Into<String>) {
1094 self.components.clear();
1095 self.text = Some(text.into());
1096 }
1097
1098 pub fn explicit_text(&self) -> Option<String> {
1102 if self.is_composite() {
1103 return Some(self.text());
1104 }
1105 self.text.clone()
1106 }
1107
1108 pub fn clear_text(&mut self) {
1112 self.components.clear();
1113 self.text = None;
1114 }
1115
1116 pub fn is_composite(&self) -> bool {
1119 !self.components.is_empty()
1120 }
1121
1122 pub fn components(&self) -> &[Lyric] {
1124 &self.components
1125 }
1126
1127 pub fn set_components(&mut self, components: Vec<Lyric>) {
1130 self.components = components;
1131 }
1132
1133 pub fn elision_before(&self) -> &str {
1136 &self.elision_before
1137 }
1138
1139 pub fn set_elision_before(&mut self, elision: impl Into<String>) {
1141 self.elision_before = elision.into();
1142 }
1143
1144 pub fn number(&self) -> IntegerType {
1146 self.number
1147 }
1148
1149 pub fn set_number(&mut self, number: IntegerType) {
1157 self.number = number;
1158 }
1159
1160 pub fn syllabic(&self) -> Syllabic {
1163 if self.is_composite() {
1164 return Syllabic::Composite;
1165 }
1166 self.syllabic
1167 }
1168
1169 pub fn explicit_syllabic(&self) -> Option<Syllabic> {
1172 if self.is_composite() {
1173 return Some(Syllabic::Composite);
1174 }
1175 self.said_syllabic.then_some(self.syllabic)
1176 }
1177
1178 pub fn set_syllabic(&mut self, syllabic: Syllabic) {
1180 self.syllabic = syllabic;
1181 self.said_syllabic = true;
1182 }
1183
1184 pub fn identifier(&self) -> String {
1187 self.identifier
1188 .clone()
1189 .unwrap_or_else(|| self.number.to_string())
1190 }
1191
1192 pub fn explicit_identifier(&self) -> Option<&str> {
1196 self.identifier.as_deref()
1197 }
1198
1199 pub fn set_identifier(&mut self, identifier: Option<String>) {
1201 self.identifier = identifier;
1202 }
1203
1204 pub fn raw_text(&self) -> String {
1210 if self.explicit_text().is_none() {
1211 return String::new();
1212 }
1213 let text = self.text();
1214 let (Some(first), Some(last)) = (self.components.first(), self.components.last()) else {
1215 return match self.syllabic {
1216 Syllabic::Begin => format!("{text}-"),
1217 Syllabic::Middle => format!("-{text}-"),
1218 Syllabic::End => format!("-{text}"),
1219 Syllabic::Single | Syllabic::Composite => text,
1220 };
1221 };
1222 let leading = matches!(first.syllabic(), Syllabic::Middle | Syllabic::End);
1223 let trailing = matches!(last.syllabic(), Syllabic::Begin | Syllabic::Middle);
1224 format!(
1225 "{}{text}{}",
1226 if leading { "-" } else { "" },
1227 if trailing { "-" } else { "" }
1228 )
1229 }
1230
1231 pub fn set_raw_text(&mut self, raw_text: &str) {
1234 self.set_text_and_syllabic(raw_text, false);
1235 }
1236
1237 pub fn set_text_and_syllabic(&mut self, raw_text: &str, apply_raw: bool) {
1243 if apply_raw {
1244 self.set_text(raw_text);
1245 if !self.said_syllabic {
1246 self.set_syllabic(Syllabic::Single);
1247 }
1248 return;
1249 }
1250 self.components.clear();
1251 let starts = raw_text.starts_with('-');
1252 let ends = raw_text.ends_with('-') && raw_text.len() > 1;
1253 let trimmed = raw_text.strip_prefix('-').unwrap_or(raw_text).to_string();
1254 let trimmed = if ends {
1255 trimmed.strip_suffix('-').unwrap_or(&trimmed).to_string()
1256 } else {
1257 trimmed
1258 };
1259 self.set_syllabic(match (starts, ends) {
1260 (true, true) => Syllabic::Middle,
1261 (true, false) => Syllabic::End,
1262 (false, true) => Syllabic::Begin,
1263 (false, false) => Syllabic::Single,
1264 });
1265 self.text = Some(trimmed);
1266 }
1267}
1268
1269impl fmt::Display for Lyric {
1270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1271 f.write_str(&self.text())
1272 }
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277 #[test]
1278 fn ties_beams_and_placements_are_written_and_read_by_name() {
1279 use super::{
1280 Beam, BeamDirection, BeamType, Beams, Lyric, Placement, Syllabic, Tie, TieStyle,
1281 TieType,
1282 };
1283 use std::str::FromStr;
1284
1285 let mut tie = Tie::from_str("start").unwrap();
1286 assert_eq!(tie.tie_type(), TieType::Start);
1287 tie.set_tie_type(TieType::Stop);
1288 assert_eq!(tie.tie_type(), TieType::Stop);
1289 assert!(Tie::from_str("sideways").is_err());
1290 assert_eq!(TieStyle::Dotted.to_string(), "dotted");
1291 assert_eq!(Placement::Above.to_string(), "above");
1292 assert_eq!(Syllabic::Middle.to_string(), "middle");
1293
1294 assert_eq!(
1295 BeamType::from_music21_name("partial"),
1296 Some(BeamType::PartialBeam)
1297 );
1298 assert_eq!(BeamType::from_music21_name("sideways"), None);
1299 assert_eq!(
1300 BeamDirection::from_music21_name("left"),
1301 Some(BeamDirection::Left)
1302 );
1303 assert_eq!(BeamDirection::from_music21_name("up"), None);
1304
1305 let mut beam = Beam::new(BeamType::Start, None);
1306 beam.set_beam_type(Some(BeamType::Continue));
1307 beam.set_direction(Some(BeamDirection::Right));
1308 assert_eq!(beam.beam_type(), Some(BeamType::Continue));
1309 assert_eq!(beam.direction(), Some(BeamDirection::Right));
1310
1311 let mut beams = Beams::default();
1312 assert!(beams.is_empty());
1313 beams.set_beams(vec![beam]);
1314 assert_eq!(beams.beams().len(), 1);
1315 beams.beams_mut()[0].set_beam_type(Some(BeamType::Stop));
1316 assert_eq!(beams.beams()[0].beam_type(), Some(BeamType::Stop));
1317 assert!(!beams.feathered());
1318 beams.set_feathered(true);
1319 assert!(beams.feathered());
1320
1321 let mut lyric = Lyric::new("hel");
1322 assert_eq!(lyric.elision_before(), " ");
1323 assert_eq!(lyric.explicit_syllabic(), Some(Syllabic::Single));
1324 lyric.clear_text();
1325 assert_eq!(lyric.explicit_text(), None);
1326 assert_eq!(Lyric::unsung().explicit_syllabic(), None);
1327 }
1328
1329 #[test]
1332 fn the_beam_passes_tidy_a_run_of_beams() {
1333 use super::{
1334 BeamDirection, BeamType, Beams, merge_connecting_partial_beams,
1335 remove_sandwiched_unbeamables, sanitize_partial_beams,
1336 };
1337 use crate::duration::DurationType;
1338
1339 let filled = |beam_type: BeamType| {
1340 let mut beams = Beams::default();
1341 beams.fill(DurationType::Eighth, Some(beam_type)).unwrap();
1342 Some(beams)
1343 };
1344 let partial = |direction: BeamDirection| {
1345 let mut beams = Beams::default();
1346 beams
1347 .fill(DurationType::Eighth, Some(BeamType::PartialBeam))
1348 .unwrap();
1349 beams.beams_mut()[0].set_direction(Some(direction));
1350 Some(beams)
1351 };
1352
1353 let mut run = vec![
1355 None,
1356 filled(BeamType::Start),
1357 None,
1358 filled(BeamType::Start),
1359 filled(BeamType::Stop),
1360 ];
1361 remove_sandwiched_unbeamables(&mut run);
1362 assert!(run[1].is_none());
1363 assert!(run[3].is_some());
1364
1365 let mut lone = vec![partial(BeamDirection::Left)];
1368 sanitize_partial_beams(&mut lone);
1369 assert!(lone[0].is_none());
1370 let mut mixed = Beams::default();
1371 mixed.append(BeamType::Start, None);
1372 mixed.append(BeamType::PartialBeam, Some(BeamDirection::Left));
1373 let mut run = vec![Some(mixed)];
1374 sanitize_partial_beams(&mut run);
1375 let group = run[0].as_ref().unwrap();
1376 assert_eq!(group.beams()[1].direction(), Some(BeamDirection::Right));
1377
1378 let mut run = vec![partial(BeamDirection::Right), partial(BeamDirection::Left)];
1380 merge_connecting_partial_beams(&mut run);
1381 assert_eq!(
1382 run[0].as_ref().unwrap().beams()[0].beam_type(),
1383 Some(BeamType::Start)
1384 );
1385 assert_eq!(
1386 run[1].as_ref().unwrap().beams()[0].beam_type(),
1387 Some(BeamType::Stop)
1388 );
1389 let mut run = vec![partial(BeamDirection::Right), filled(BeamType::Start)];
1391 merge_connecting_partial_beams(&mut run);
1392 assert_eq!(
1393 run[1].as_ref().unwrap().beams()[0].beam_type(),
1394 Some(BeamType::Continue)
1395 );
1396 let mut run = vec![partial(BeamDirection::Right), filled(BeamType::Stop)];
1398 merge_connecting_partial_beams(&mut run);
1399 assert_eq!(
1400 run[0].as_ref().unwrap().beams()[0].beam_type(),
1401 Some(BeamType::PartialBeam)
1402 );
1403 }
1404
1405 #[test]
1407 fn text_and_syllabic_are_read_from_the_hyphens_unless_told_not_to() {
1408 use super::{Lyric, Syllabic};
1409
1410 let mut lyric = Lyric::unsung();
1411 lyric.set_text_and_syllabic("hel-", false);
1412 assert_eq!(
1413 (lyric.text(), lyric.syllabic()),
1414 ("hel".to_string(), Syllabic::Begin)
1415 );
1416 lyric.set_text_and_syllabic("-lo", false);
1417 assert_eq!(
1418 (lyric.text(), lyric.syllabic()),
1419 ("lo".to_string(), Syllabic::End)
1420 );
1421 lyric.set_text_and_syllabic("the", false);
1422 assert_eq!(
1423 (lyric.text(), lyric.syllabic()),
1424 ("the".to_string(), Syllabic::Single)
1425 );
1426
1427 let mut raw = Lyric::unsung();
1428 raw.set_text_and_syllabic("hel-", true);
1429 assert_eq!(
1430 (raw.text(), raw.syllabic()),
1431 ("hel-".to_string(), Syllabic::Single)
1432 );
1433 raw.set_syllabic(Syllabic::Begin);
1434 raw.set_text_and_syllabic("-lo", true);
1435 assert_eq!(
1436 (raw.text(), raw.syllabic()),
1437 ("-lo".to_string(), Syllabic::Begin)
1438 );
1439 }
1440
1441 #[test]
1442 fn beams_fill_one_level_for_each_flag() {
1443 let mut beams = Beams::new();
1445 beams
1446 .fill(DurationType::Sixteenth, Some(BeamType::Start))
1447 .unwrap();
1448 assert_eq!(beams.len(), 2);
1449 assert_eq!(
1450 beams.types(),
1451 [Some(BeamType::Start), Some(BeamType::Start)]
1452 );
1453 assert_eq!(beams.numbers(), [Some(1), Some(2)]);
1454 assert_eq!(
1455 beams.to_string(),
1456 "<music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>"
1457 );
1458
1459 beams.set_all(BeamType::Stop, None);
1460 assert_eq!(beams.types(), [Some(BeamType::Stop), Some(BeamType::Stop)]);
1461 assert_eq!(
1462 beams.by_number(1).and_then(Beam::beam_type),
1463 Some(BeamType::Stop)
1464 );
1465
1466 let mut counted = Beams::new();
1469 counted.fill(DurationType::Sixteenth, None).unwrap();
1470 assert_eq!(counted.types(), [None, None]);
1471 assert_eq!(
1472 counted.to_string(),
1473 "<music21.beam.Beam 1/None>/<music21.beam.Beam 2/None>"
1474 );
1475 assert!(Beams::new().fill_levels(12, None).is_err());
1476 assert!(beams.set_by_number(3, BeamType::Start, None).is_err());
1477
1478 assert!(Beams::new().fill(DurationType::Quarter, None).is_err());
1480
1481 let mut stub = Beams::new();
1483 stub.append(BeamType::PartialBeam, Some(BeamDirection::Left));
1484 assert_eq!(stub.to_string(), "<music21.beam.Beam 1/partial/left>");
1485 }
1486 use super::*;
1487
1488 #[test]
1489 fn tie_types_round_trip_and_reject_others() {
1490 for tie_type in TieType::ALL {
1491 assert_eq!(TieType::from_name(tie_type.as_str()).unwrap(), tie_type);
1492 }
1493 assert_eq!(Tie::default().tie_type(), TieType::Start);
1494 assert_eq!(Tie::from_name("stop").unwrap().to_string(), "Tie stop");
1495 let error = TieType::from_name("hello").unwrap_err().to_string();
1496 assert!(error.contains("Type must be one of"), "{error}");
1497 assert!(error.ends_with("not hello"), "{error}");
1498 }
1499
1500 #[test]
1501 fn tie_carries_style_and_placement() {
1502 let mut tie = Tie::new(TieType::Continue);
1503 assert_eq!(tie.style(), TieStyle::Normal);
1504 assert_eq!(tie.placement(), None);
1505 tie.set_style(TieStyle::Dashed);
1506 tie.set_placement(Some(Placement::Above));
1507 assert_eq!(tie.style().as_str(), "dashed");
1508 assert_eq!(tie.placement().map(Placement::as_str), Some("above"));
1509 assert!(TieStyle::from_name("wavy").is_err());
1510 assert!(Placement::from_name("sideways").is_err());
1511 }
1512
1513 #[test]
1514 fn notehead_and_stem_names_round_trip() {
1515 for notehead in Notehead::ALL {
1516 assert_eq!(Notehead::from_name(notehead.as_str()).unwrap(), notehead);
1517 }
1518 for direction in StemDirection::ALL {
1519 assert_eq!(
1520 StemDirection::from_name(direction.as_str()).unwrap(),
1521 direction
1522 );
1523 }
1524 assert_eq!(Notehead::default(), Notehead::Normal);
1525 assert_eq!(
1527 StemDirection::from_name("none").unwrap(),
1528 StemDirection::NoStem
1529 );
1530 assert_eq!(StemDirection::NAMES.len(), StemDirection::ALL.len() + 1);
1531 assert_eq!(StemDirection::default(), StemDirection::Unspecified);
1532 assert_eq!(Notehead::Diamond.to_string(), "diamond");
1533 assert_eq!(StemDirection::NoStem.to_string(), "noStem");
1534 assert!(Notehead::from_name("blah").is_err());
1535 assert!(StemDirection::from_name("sideways").is_err());
1536 }
1537
1538 #[test]
1539 fn lyric_hyphens_say_where_the_syllable_falls() {
1540 let cases = [
1541 ("hello", Syllabic::Single, "hello"),
1542 ("dic-", Syllabic::Begin, "dic"),
1543 ("-ci-", Syllabic::Middle, "ci"),
1544 ("-us", Syllabic::End, "us"),
1545 ];
1546 for (raw, syllabic, text) in cases {
1547 let lyric = Lyric::from_raw_text(raw);
1548 assert_eq!(lyric.syllabic(), syllabic, "{raw}");
1549 assert_eq!(lyric.text(), text, "{raw}");
1550 assert_eq!(lyric.raw_text(), raw, "{raw}");
1551 }
1552 let mut lyric = Lyric::new("shine");
1553 assert_eq!(lyric.number(), 1);
1554 assert_eq!(lyric.identifier(), "1");
1555 lyric.set_number(3);
1556 assert_eq!(lyric.identifier(), "3");
1557 lyric.set_identifier(Some("chorus".to_string()));
1558 assert_eq!(lyric.identifier(), "chorus");
1559 lyric.set_number(0);
1562 assert_eq!(lyric.number(), 0);
1563 assert_eq!(lyric.to_string(), "shine");
1564 assert_eq!(Syllabic::from_name("middle").unwrap(), Syllabic::Middle);
1565 assert!(Syllabic::from_name("half").is_err());
1566 }
1567
1568 #[test]
1569 fn a_composite_lyric_reads_as_its_components_run_together() {
1570 let mut co = Lyric::new("co");
1572 co.set_syllabic(Syllabic::End);
1573 let mut e = Lyric::new("e");
1574 e.set_syllabic(Syllabic::Single);
1575
1576 let mut bianco = Lyric::new("");
1577 assert!(!bianco.is_composite());
1578 bianco.set_components(vec![co, e]);
1579 assert!(bianco.is_composite());
1580 assert_eq!(bianco.text(), "co e");
1581 assert_eq!(bianco.syllabic(), Syllabic::Composite);
1582 assert_eq!(bianco.raw_text(), "-co e");
1584
1585 let mut components = bianco.components().to_vec();
1588 components[1].set_elision_before("_");
1589 components[1].set_syllabic(Syllabic::Middle);
1590 bianco.set_components(components);
1591 assert_eq!(bianco.text(), "co_e");
1592 assert_eq!(bianco.raw_text(), "-co_e-");
1593
1594 bianco.set_text("bianco");
1596 assert!(!bianco.is_composite());
1597 assert_eq!(bianco.text(), "bianco");
1598 assert_eq!(bianco.syllabic(), Syllabic::Single);
1599 }
1600
1601 #[test]
1602 fn an_identifier_is_only_there_when_it_was_named() {
1603 let mut lyric = Lyric::new("shine");
1604 assert_eq!(lyric.explicit_identifier(), None);
1605 assert_eq!(lyric.identifier(), "1");
1606 lyric.set_identifier(Some("chorus".to_string()));
1607 assert_eq!(lyric.explicit_identifier(), Some("chorus"));
1608 }
1609
1610 #[test]
1611 fn a_lone_hyphen_is_its_own_syllable() {
1612 let lyric = Lyric::from_raw_text("-");
1616 assert_eq!(lyric.syllabic(), Syllabic::End);
1617 assert_eq!(lyric.text(), "");
1618 }
1619}