1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::fmt;
use std::str::FromStr;

use crate::{Semitones, StaffSteps};

/// Custom error for strings that cannot be parsed into intervals.
#[derive(Debug)]
pub struct ParseIntervalError {
    name: String,
}

impl fmt::Display for ParseIntervalError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Could not parse interval name \"{}\"", self.name)
    }
}

/// An interval is the difference between two notes.
/// https://en.wikipedia.org/wiki/Interval_(music)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interval {
    PerfectUnison,
    MajorSecond,
    MinorThird,
    MajorThird,
    PerfectFourth,
    DiminishedFifth,
    PerfectFifth,
    AugmentedFifth,
    MajorSixth,
    DiminishedSeventh,
    MinorSeventh,
    MajorSeventh,
    MinorNinth,
    MajorNinth,
    AugmentedNinth,
    PerfectEleventh,
    MajorThirteenth,
}

impl Interval {
    /// Return the number of semitones that the interval encompasses.
    pub fn to_semitones(self) -> Semitones {
        use Interval::*;

        match self {
            PerfectUnison => 0,
            MajorSecond => 2,
            MinorThird => 3,
            MajorThird => 4,
            PerfectFourth => 5,
            DiminishedFifth => 6,
            PerfectFifth => 7,
            AugmentedFifth => 8,
            MajorSixth => 9,
            DiminishedSeventh => 9,
            MinorSeventh => 10,
            MajorSeventh => 11,
            MinorNinth => 13,
            MajorNinth => 14,
            AugmentedNinth => 15,
            PerfectEleventh => 17,
            MajorThirteenth => 21,
        }
    }

    /// Return the interval's number. It corresponds to the number of
    /// staff positions that the intervall encompasses.
    pub fn to_number(self) -> StaffSteps {
        use Interval::*;

        match self {
            PerfectUnison => 1,
            MajorSecond => 2,
            MinorThird => 3,
            MajorThird => 3,
            PerfectFourth => 4,
            DiminishedFifth => 5,
            PerfectFifth => 5,
            AugmentedFifth => 5,
            MajorSixth => 6,
            DiminishedSeventh => 7,
            MinorSeventh => 7,
            MajorSeventh => 7,
            MinorNinth => 9,
            MajorNinth => 9,
            AugmentedNinth => 9,
            PerfectEleventh => 11,
            MajorThirteenth => 13,
        }
    }
}

impl FromStr for Interval {
    type Err = ParseIntervalError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use Interval::*;

        let name = s.to_string();

        let interval = match s {
            "P1" => PerfectUnison,
            "M2" => MajorSecond,
            "m3" => MinorThird,
            "M3" => MajorThird,
            "P4" => PerfectFourth,
            "d5" => DiminishedFifth,
            "P5" => PerfectFifth,
            "A5" => AugmentedFifth,
            "M6" => MajorSixth,
            "d7" => DiminishedSeventh,
            "m7" => MinorSeventh,
            "M7" => MajorSeventh,
            "m9" => MinorNinth,
            "M9" => MajorNinth,
            "A9" => AugmentedNinth,
            "P11" => PerfectEleventh,
            "M13" => MajorThirteenth,
            _ => return Err(ParseIntervalError { name }),
        };

        Ok(interval)
    }
}