pychord

 1from pychord.const import *
 2from pychord.interval import *
 3from pychord.mode import *
 4from pychord.note import *
 5from pychord.ratio import *
 6from pychord.tone import *
 7
 8__all__ = [
 9    # Classes
10    "Tone",
11    "Ratio",
12    "Interval",
13    "Note",
14    "Mode",
15    "Scale",
16    # Constants
17    "OCTAVE_RATIO",
18    "SEMITONE_RATIO",
19    "C0_FREQUENCY",
20    "SEMITONE",
21    "MINOR_SECOND",
22    "WHOLETONE",
23    "MAJOR_SECOND",
24    "MINOR_THIRD",
25    "MAJOR_THIRD",
26    "PERFECT_FOURTH",
27    "AUGMENTED_FOURTH",
28    "TRITONE",
29    "DIMINISHED_FIFTH",
30    "PERFECT_FIFTH",
31    "MINOR_SIXTH",
32    "MAJOR_SIXTH",
33    "MINOR_SEVENTH",
34    "MAJOR_SEVENTH",
35    "OCTAVE",
36    "IONIAN",
37    "PHRYGIAN",
38    "DORIAN",
39    "LYDIAN",
40    "MIXOLYDIAN",
41    "AEOLIAN",
42    "LOCRIAN",
43]
class Tone:
 7class Tone:
 8    """
 9    Describes an abstract musical frequency
10    """
11
12    frequency: Union[int, float]
13    "The frequency of the tone in hertz"
14
15    def __init__(self, frequency: Union[int, float]):
16        self.frequency = frequency
17
18    def __repr__(self) -> str:
19        return f"[Tone ({self.frequency:.4f})]"
20
21    def __str__(self) -> str:
22        return self.__repr__()
23
24    def __add__(self, other: Ratio) -> "Tone":
25        if not isinstance(other, Ratio):
26            return NotImplemented
27        return self.transposed(other)
28
29    def __sub__(self, other: Union[Ratio, "Tone"]) -> Union[Ratio, "Tone"]:
30        if isinstance(other, Ratio):
31            return self.transposed(-other)
32        elif isinstance(other, Tone):
33            return Ratio(self.frequency / other.frequency)
34
35        return NotImplemented
36
37    def __eq__(self, other: "Tone"):
38        return isinstance(other, Tone) and self.frequency == other.frequency
39
40    def __ne__(self, other: "Tone"):
41        return not isinstance(other, Tone) or self.frequency != other.frequency
42
43    def __ge__(self, other: "Tone") -> bool:
44        if not isinstance(other, Tone):
45            return NotImplemented
46        return self.frequency >= other.frequency
47
48    def __gt__(self, other: "Tone") -> bool:
49        if not isinstance(other, Tone):
50            return NotImplemented
51        return self.frequency > other.frequency
52
53    def __le__(self, other: "Tone") -> bool:
54        if not isinstance(other, Tone):
55            return NotImplemented
56        return self.frequency <= other.frequency
57
58    def __lt__(self, other: "Tone") -> bool:
59        if not isinstance(other, Tone):
60            return NotImplemented
61        return self.frequency < other.frequency
62
63    def transposed(self, ratio: "Ratio") -> "Tone":
64        """
65        Multiply the tone frequency by the input ratio
66        """
67
68        if not isinstance(ratio, Ratio):
69            raise TypeError()
70        return Tone(self.frequency * ratio.ratio)

Describes an abstract musical frequency

Tone(frequency: Union[int, float])
15    def __init__(self, frequency: Union[int, float]):
16        self.frequency = frequency
frequency: Union[int, float]

The frequency of the tone in hertz

def transposed(self, ratio: Ratio) -> Tone:
63    def transposed(self, ratio: "Ratio") -> "Tone":
64        """
65        Multiply the tone frequency by the input ratio
66        """
67
68        if not isinstance(ratio, Ratio):
69            raise TypeError()
70        return Tone(self.frequency * ratio.ratio)

Multiply the tone frequency by the input ratio

class Ratio:
 8class Ratio:
 9    """
10    Describes an abstract interval between two `Tone`s, the ratio between their frequencies
11    """
12
13    ratio: Union[float, Fraction]
14    "Simple mathematical ratio between `Tone`s"
15
16    def __init__(self, ratio: Union[float, Fraction]):
17        self.ratio = ratio
18
19    def __repr__(self):
20        return f"[Ratio {self.ratio:.4f}]"
21
22    def __str__(self):
23        return self.__repr__()
24
25    def __neg__(self) -> "Ratio":
26        return self.inversion()
27
28    def __add__(self, other: "Ratio"):
29        if not isinstance(other, Ratio):
30            return NotImplemented
31        return Ratio(self.ratio * other.ratio)
32
33    def __sub__(self, other: "Ratio"):
34        if not isinstance(other, Ratio):
35            return NotImplemented
36        return Ratio(self.ratio / other.ratio)
37
38    def __mul__(self, other: Union[int, float, Fraction]) -> "Ratio":
39        if not isinstance(other, (int, float, Fraction)):
40            return NotImplemented
41        return Ratio(self.ratio**other)
42
43    def __eq__(self, other: "Ratio"):
44        return isinstance(other, Ratio) and self.ratio == other.ratio
45
46    def __ne__(self, other: "Ratio"):
47        return not isinstance(other, Ratio) or self.ratio != other.ratio
48
49    def __ge__(self, other: "Ratio") -> bool:
50        if not isinstance(other, (Ratio)):
51            return NotImplemented
52        return self.ratio >= other.ratio
53
54    def __gt__(self, other: "Ratio") -> bool:
55        if not isinstance(other, (Ratio)):
56            return NotImplemented
57        return self.ratio > other.ratio
58
59    def __le__(self, other: "Ratio") -> bool:
60        if not isinstance(other, (Ratio)):
61            return NotImplemented
62        return self.ratio <= other.ratio
63
64    def __lt__(self, other: "Ratio") -> bool:
65        if not isinstance(other, (Ratio)):
66            return NotImplemented
67        return self.ratio < other.ratio
68
69    def compliment(self) -> "Ratio":
70        """
71        Compliment of `Ratio`, when added to the original `Ratio` will equal an octave
72        """
73
74        return -(self - OCTAVE_RATIO)
75
76    def inversion(self) -> "Ratio":
77        """
78        Inversion of `Ratio` e.g. up a fifth becomes down a fifth
79        """
80
81        return Ratio(1.0 / self.ratio)

Describes an abstract interval between two Tones, the ratio between their frequencies

Ratio(ratio: Union[float, fractions.Fraction])
16    def __init__(self, ratio: Union[float, Fraction]):
17        self.ratio = ratio
ratio: Union[float, fractions.Fraction]

Simple mathematical ratio between Tones

def compliment(self) -> Ratio:
69    def compliment(self) -> "Ratio":
70        """
71        Compliment of `Ratio`, when added to the original `Ratio` will equal an octave
72        """
73
74        return -(self - OCTAVE_RATIO)

Compliment of Ratio, when added to the original Ratio will equal an octave

def inversion(self) -> Ratio:
76    def inversion(self) -> "Ratio":
77        """
78        Inversion of `Ratio` e.g. up a fifth becomes down a fifth
79        """
80
81        return Ratio(1.0 / self.ratio)

Inversion of Ratio e.g. up a fifth becomes down a fifth

class Interval(pychord.Ratio):
  9class Interval(Ratio):
 10    """
 11    Describes a musical interval as a ratio quantized to a 12TET semitone
 12    """
 13
 14    semitones: int
 15    "Integer number of 12TET semitones"
 16
 17    quality: str
 18    "Quality of `Interval`, 'M' for major, 'm' for minor, 'P' for perfect, 'A' for augmented, 'd' for diminished"
 19
 20    quantity: int
 21    "Quantity of `Interval` as an integer number of major scale steps, for example M10 quality is 10"
 22
 23    def __init__(self, interval: Union[int, str]):
 24        """
 25        `interval` can be either an interval name like "M5" "m2" "d5" "A2" "P5" or an integer number of semitones
 26        """
 27
 28        assert isinstance(interval, (int, str))
 29
 30        if isinstance(interval, int):
 31            self.semitones = interval
 32
 33            abs_semitones = abs(self.semitones)
 34
 35            self.quality = INTERVAL_VALUE_TO_COMPONENTS[abs_semitones % SEMITONES_PER_OCTAVE][0]
 36
 37            self.quantity = INTERVAL_VALUE_TO_COMPONENTS[abs_semitones % SEMITONES_PER_OCTAVE][1] + (
 38                7 * (abs_semitones // SEMITONES_PER_OCTAVE)
 39            )
 40
 41        elif isinstance(interval, str):
 42            m = INTERVAL_NAME_RE.match(interval)
 43
 44            assert m is not None, f"Invalid 12TET interval name: '{interval}'!"
 45
 46            self.quality = m.group(1)
 47            self.quantity = int(m.group(2))
 48            octave = 0
 49            offset_quantity = self.quantity
 50
 51            if offset_quantity > 7:
 52                octave = (self.quantity - 1) // 7
 53                offset_quantity = ((self.quantity - 1) % 7) + 1
 54
 55            offset_name = f"{self.quality}{offset_quantity}"
 56
 57            if offset_name not in INTERVAL_NAME_TO_VALUE:
 58                raise ValueError(f"Invalid 12TET interval name: '{interval}'!")
 59
 60            self.semitones = octave * SEMITONES_PER_OCTAVE + INTERVAL_NAME_TO_VALUE[offset_name]
 61
 62        interval = (SEMITONE_RATIO * (abs(self.semitones) % SEMITONES_PER_OCTAVE)) + (
 63            OCTAVE_RATIO * (abs(self.semitones) // SEMITONES_PER_OCTAVE)
 64        )
 65        super().__init__((interval if self.semitones >= 0 else -interval).ratio)
 66
 67    def __repr__(self) -> str:
 68        return f"[Interval {self.name()} ({self.ratio:.4f})]"
 69
 70    def __str__(self) -> str:
 71        return self.__repr__()
 72
 73    def __add__(self, other: Union["Interval", Ratio]) -> Union["Interval", Ratio]:
 74        if not isinstance(other, (Interval, Ratio)):
 75            return NotImplemented
 76
 77        if isinstance(other, Interval):
 78            return Interval(self.semitones + other.semitones)
 79        elif isinstance(other, Ratio):
 80            return Ratio(self.ratio) + other
 81
 82    def __sub__(self, other: Union["Interval", Ratio]) -> Union["Interval", Ratio]:
 83        if not isinstance(other, (Interval, Ratio)):
 84            return NotImplemented
 85
 86        if isinstance(other, Interval):
 87            return Interval(self.semitones - other.semitones)
 88        elif isinstance(other, Ratio):
 89            return Ratio(self.ratio) - other
 90
 91    def __mul__(self, other: Union[int, float, Fraction]) -> Union["Interval", Ratio]:
 92        if not isinstance(other, (int, float, Fraction)):
 93            return NotImplemented
 94
 95        if isinstance(other, int):
 96            return Interval(self.semitones * other)
 97        else:
 98            return Ratio(self.ratio) * other
 99
100    def __neg__(self) -> "Ratio":
101        """
102        Inversion of `Interval` e.g. up an octave becomes down an octave
103        """
104        return Interval(-self.semitones)
105
106    def compliment(self) -> "Interval":
107        """
108        Compliment of `Interval`, when added to the original interval will equal an octave
109        """
110        return -(self - Interval(SEMITONES_PER_OCTAVE))
111
112    def name(self) -> str:
113        """
114        Return name of `Interval` like P5 or m7 or -M3
115        """
116        return f"{'-' if self.semitones < 0 else ''}{self.quality}{self.quantity}"
117
118    def decompound(self) -> "Interval":
119        """
120        Returns the same `Interval` without any octave offset
121        """
122        return Interval(self.semitones % SEMITONES_PER_OCTAVE)

Describes a musical interval as a ratio quantized to a 12TET semitone

Interval(interval: Union[int, str])
23    def __init__(self, interval: Union[int, str]):
24        """
25        `interval` can be either an interval name like "M5" "m2" "d5" "A2" "P5" or an integer number of semitones
26        """
27
28        assert isinstance(interval, (int, str))
29
30        if isinstance(interval, int):
31            self.semitones = interval
32
33            abs_semitones = abs(self.semitones)
34
35            self.quality = INTERVAL_VALUE_TO_COMPONENTS[abs_semitones % SEMITONES_PER_OCTAVE][0]
36
37            self.quantity = INTERVAL_VALUE_TO_COMPONENTS[abs_semitones % SEMITONES_PER_OCTAVE][1] + (
38                7 * (abs_semitones // SEMITONES_PER_OCTAVE)
39            )
40
41        elif isinstance(interval, str):
42            m = INTERVAL_NAME_RE.match(interval)
43
44            assert m is not None, f"Invalid 12TET interval name: '{interval}'!"
45
46            self.quality = m.group(1)
47            self.quantity = int(m.group(2))
48            octave = 0
49            offset_quantity = self.quantity
50
51            if offset_quantity > 7:
52                octave = (self.quantity - 1) // 7
53                offset_quantity = ((self.quantity - 1) % 7) + 1
54
55            offset_name = f"{self.quality}{offset_quantity}"
56
57            if offset_name not in INTERVAL_NAME_TO_VALUE:
58                raise ValueError(f"Invalid 12TET interval name: '{interval}'!")
59
60            self.semitones = octave * SEMITONES_PER_OCTAVE + INTERVAL_NAME_TO_VALUE[offset_name]
61
62        interval = (SEMITONE_RATIO * (abs(self.semitones) % SEMITONES_PER_OCTAVE)) + (
63            OCTAVE_RATIO * (abs(self.semitones) // SEMITONES_PER_OCTAVE)
64        )
65        super().__init__((interval if self.semitones >= 0 else -interval).ratio)

interval can be either an interval name like "M5" "m2" "d5" "A2" "P5" or an integer number of semitones

semitones: int

Integer number of 12TET semitones

quality: str

Quality of Interval, 'M' for major, 'm' for minor, 'P' for perfect, 'A' for augmented, 'd' for diminished

quantity: int

Quantity of Interval as an integer number of major scale steps, for example M10 quality is 10

def compliment(self) -> Interval:
106    def compliment(self) -> "Interval":
107        """
108        Compliment of `Interval`, when added to the original interval will equal an octave
109        """
110        return -(self - Interval(SEMITONES_PER_OCTAVE))

Compliment of Interval, when added to the original interval will equal an octave

def name(self) -> str:
112    def name(self) -> str:
113        """
114        Return name of `Interval` like P5 or m7 or -M3
115        """
116        return f"{'-' if self.semitones < 0 else ''}{self.quality}{self.quantity}"

Return name of Interval like P5 or m7 or -M3

def decompound(self) -> Interval:
118    def decompound(self) -> "Interval":
119        """
120        Returns the same `Interval` without any octave offset
121        """
122        return Interval(self.semitones % SEMITONES_PER_OCTAVE)

Returns the same Interval without any octave offset

class Note(pychord.Tone):
 10class Note(Tone):
 11    """
 12    Describes a musical note as a Tone quantized to 12TET with A4 = 440Hz, where note 0 = C0
 13    """
 14
 15    letter: str
 16    "The alphabet letter of the note, A-G"
 17
 18    octave: int
 19    "The octave of the note, octaves start at C and end at B"
 20
 21    accidental: int
 22    "The accidental semitone value, 0 for natural, 1 for sharp, -1 for flat, 2 for double sharp, -2 for double flat"
 23
 24    semitone: int
 25    "The absolute semitone of the note starting at C0=0"
 26
 27    def __init__(self, note: Union[int, str]):
 28        """
 29        `note` can either be a note name like "Ab4" "G2" "C#6" "F" or an integer number of semitones from C0
 30        """
 31
 32        if not isinstance(note, (int, str)):
 33            raise TypeError()
 34
 35        if isinstance(note, int):
 36            self.semitone = note
 37            self.octave = self.semitone // SEMITONES_PER_OCTAVE
 38            self.letter = NOTE_SEMITONE_TO_COMPONENTS[self.semitone % SEMITONES_PER_OCTAVE][0]
 39            self.accidental = NOTE_SEMITONE_TO_COMPONENTS[self.semitone % SEMITONES_PER_OCTAVE][1]
 40
 41        elif isinstance(note, str):
 42            name = note
 43            m = NOTE_NAME_RE.match(name)
 44
 45            if m is None:
 46                raise ValueError(f"Invalid note name '{name}'!")
 47
 48            self.letter = m.group(1)
 49            self.accidental = ACCIDENTAL_NAME_TO_VALUE[m.group(2)]
 50            self.octave = int(m.group(3) or NOTE_DEFAULT_OCTAVE)
 51
 52            c_based_note_semitone = NOTE_NAME_TO_SEMITONE[self.letter]
 53
 54            self.semitone = c_based_note_semitone + self.accidental + SEMITONES_PER_OCTAVE * self.octave
 55
 56        super().__init__(
 57            (
 58                Tone(C0_FREQUENCY)
 59                + Interval(self.semitone % SEMITONES_PER_OCTAVE)
 60                + (OCTAVE * (self.semitone // SEMITONES_PER_OCTAVE))
 61            ).frequency
 62        )
 63
 64    def __repr__(self) -> str:
 65        return f"[Note {self.name()} ({self.frequency:.4f})]"
 66
 67    def __str__(self) -> str:
 68        return self.__repr__()
 69
 70    def __add__(self, other: Union[Interval, Ratio]):
 71        if not isinstance(other, (Ratio, Interval)):
 72            return NotImplemented
 73        return self.transposed(other)
 74
 75    def __sub__(self, other: Union[Interval, Ratio, "Note"]):
 76        if isinstance(other, Ratio):
 77            return self.transposed(-other)
 78        elif isinstance(other, Note):
 79            return Interval(self.semitone - other.semitone)
 80        else:
 81            return NotImplemented
 82
 83    def name(self) -> str:
 84        """
 85        Return name of note like Ab4 or G6
 86        """
 87
 88        return f"{self.letter}{ACCIDENTAL_VALUE_TO_NAME[self.accidental]}{self.octave}"
 89
 90    def transposed(self, interval: Union[Ratio, Interval]) -> Union["Note", "Tone"]:
 91        """
 92        Transpose a note by an `Interval` or `Ratio`. Passing in an `Interval` will return a `Note` while passing in a `Ratio` will return a `Tone`
 93        """
 94
 95        if not isinstance(interval, (Ratio, Interval)):
 96            raise TypeError()
 97
 98        if isinstance(interval, Interval):
 99            return Note(self.semitone + interval.semitones)
100        else:
101            return Tone(self.frequency) + interval
102
103    def following(self, note: "Note") -> "Note":
104        """
105        Return the higher octave of this `Note` following `note`
106        """
107
108        i = (self - note).decompound()
109
110        i = Interval("P8") if i.semitones == 0 else i
111
112        return note + i
113
114    def preceding(self, note: "Note") -> "Note":
115        """
116        Return the lower octave of this `Note` preceding `note`
117        """
118
119        i = -(note - self).decompound()
120
121        i = -Interval("P8") if i.semitones == 0 else i
122
123        return note + i

Describes a musical note as a Tone quantized to 12TET with A4 = 440Hz, where note 0 = C0

Note(note: Union[int, str])
27    def __init__(self, note: Union[int, str]):
28        """
29        `note` can either be a note name like "Ab4" "G2" "C#6" "F" or an integer number of semitones from C0
30        """
31
32        if not isinstance(note, (int, str)):
33            raise TypeError()
34
35        if isinstance(note, int):
36            self.semitone = note
37            self.octave = self.semitone // SEMITONES_PER_OCTAVE
38            self.letter = NOTE_SEMITONE_TO_COMPONENTS[self.semitone % SEMITONES_PER_OCTAVE][0]
39            self.accidental = NOTE_SEMITONE_TO_COMPONENTS[self.semitone % SEMITONES_PER_OCTAVE][1]
40
41        elif isinstance(note, str):
42            name = note
43            m = NOTE_NAME_RE.match(name)
44
45            if m is None:
46                raise ValueError(f"Invalid note name '{name}'!")
47
48            self.letter = m.group(1)
49            self.accidental = ACCIDENTAL_NAME_TO_VALUE[m.group(2)]
50            self.octave = int(m.group(3) or NOTE_DEFAULT_OCTAVE)
51
52            c_based_note_semitone = NOTE_NAME_TO_SEMITONE[self.letter]
53
54            self.semitone = c_based_note_semitone + self.accidental + SEMITONES_PER_OCTAVE * self.octave
55
56        super().__init__(
57            (
58                Tone(C0_FREQUENCY)
59                + Interval(self.semitone % SEMITONES_PER_OCTAVE)
60                + (OCTAVE * (self.semitone // SEMITONES_PER_OCTAVE))
61            ).frequency
62        )

note can either be a note name like "Ab4" "G2" "C#6" "F" or an integer number of semitones from C0

letter: str

The alphabet letter of the note, A-G

octave: int

The octave of the note, octaves start at C and end at B

accidental: int

The accidental semitone value, 0 for natural, 1 for sharp, -1 for flat, 2 for double sharp, -2 for double flat

semitone: int

The absolute semitone of the note starting at C0=0

def name(self) -> str:
83    def name(self) -> str:
84        """
85        Return name of note like Ab4 or G6
86        """
87
88        return f"{self.letter}{ACCIDENTAL_VALUE_TO_NAME[self.accidental]}{self.octave}"

Return name of note like Ab4 or G6

def transposed( self, interval: Union[Ratio, Interval]) -> Union[Note, Tone]:
 90    def transposed(self, interval: Union[Ratio, Interval]) -> Union["Note", "Tone"]:
 91        """
 92        Transpose a note by an `Interval` or `Ratio`. Passing in an `Interval` will return a `Note` while passing in a `Ratio` will return a `Tone`
 93        """
 94
 95        if not isinstance(interval, (Ratio, Interval)):
 96            raise TypeError()
 97
 98        if isinstance(interval, Interval):
 99            return Note(self.semitone + interval.semitones)
100        else:
101            return Tone(self.frequency) + interval

Transpose a note by an Interval or Ratio. Passing in an Interval will return a Note while passing in a Ratio will return a Tone

def following(self, note: Note) -> Note:
103    def following(self, note: "Note") -> "Note":
104        """
105        Return the higher octave of this `Note` following `note`
106        """
107
108        i = (self - note).decompound()
109
110        i = Interval("P8") if i.semitones == 0 else i
111
112        return note + i

Return the higher octave of this Note following note

def preceding(self, note: Note) -> Note:
114    def preceding(self, note: "Note") -> "Note":
115        """
116        Return the lower octave of this `Note` preceding `note`
117        """
118
119        i = -(note - self).decompound()
120
121        i = -Interval("P8") if i.semitones == 0 else i
122
123        return note + i

Return the lower octave of this Note preceding note

class Mode:
 7class Mode:
 8    """
 9    A musical `Mode` consisting of intervals making up an abstract tonicless `Scale`
10    """
11
12    intervals: list[Interval]
13    "List of `Interval`'s from the tonic of the `Mode`"
14
15    def __init__(self, intervals: list[Interval]):
16        if not isinstance(intervals, list):
17            raise TypeError()
18        self.intervals = intervals
19
20    def __repr__(self) -> str:
21        return f"[Mode {' '.join([i.name() for i in self.intervals])}]"
22
23    def __str__(self) -> str:
24        return self.__repr__()
25
26    def __eq__(self, other: "Mode"):
27        return (
28            isinstance(other, Mode)
29            and len(self.intervals) == len(other.intervals)
30            and all(x == y for x, y in zip(self.intervals, other.intervals))
31        )
32
33    def __ne__(self, other: "Mode"):
34        return (
35            not isinstance(other, Mode)
36            or len(self.intervals) != len(other.intervals)
37            or any(x != y for x, y in zip(self.intervals, other.intervals))
38        )
39
40    def __lshift__(self, other: int) -> "Mode":
41        if not isinstance(other, int):
42            return NotImplemented
43        return self.shifted(other)
44
45    def __rshift__(self, other: int) -> "Mode":
46        if not isinstance(other, int):
47            return NotImplemented
48        return self.shifted(-other)
49
50    def shifted(self, steps: int) -> "Mode":
51        """
52        Return a shifted version of this `Mode` essentially the same `Mode` but starting on step `steps`
53        """
54
55        if not isinstance(steps, int):
56            raise TypeError()
57
58        steps = steps % len(self.intervals)
59
60        shifted_intervals = []
61
62        for i in range(len(self.intervals)):
63            shifted_intervals.append(
64                (self.intervals[(i + steps) % len(self.intervals)] - self.intervals[steps]).decompound()
65            )
66
67        return Mode(shifted_intervals)
68
69    def to_scale(self, tonic: Note) -> "Scale":
70        return Scale([tonic + interval for interval in self.intervals])

A musical Mode consisting of intervals making up an abstract tonicless Scale

Mode(intervals: list[Interval])
15    def __init__(self, intervals: list[Interval]):
16        if not isinstance(intervals, list):
17            raise TypeError()
18        self.intervals = intervals
intervals: list[Interval]

List of Interval's from the tonic of the Mode

def shifted(self, steps: int) -> Mode:
50    def shifted(self, steps: int) -> "Mode":
51        """
52        Return a shifted version of this `Mode` essentially the same `Mode` but starting on step `steps`
53        """
54
55        if not isinstance(steps, int):
56            raise TypeError()
57
58        steps = steps % len(self.intervals)
59
60        shifted_intervals = []
61
62        for i in range(len(self.intervals)):
63            shifted_intervals.append(
64                (self.intervals[(i + steps) % len(self.intervals)] - self.intervals[steps]).decompound()
65            )
66
67        return Mode(shifted_intervals)

Return a shifted version of this Mode essentially the same Mode but starting on step steps

def to_scale(self, tonic: Note) -> Scale:
69    def to_scale(self, tonic: Note) -> "Scale":
70        return Scale([tonic + interval for interval in self.intervals])
class Scale:
 5class Scale:
 6    """
 7    A musical `Scale` consisting of a list of ordered `Note`s
 8    """
 9
10    notes: list[Note]
11    "List of `Note`s in the `Scale`"
12
13    def __init__(self, notes: list[Note]):
14        if not isinstance(notes, list):
15            raise TypeError()
16        self.notes = notes
17
18    def __repr__(self) -> str:
19        return f"[Scale {' '.join([n.name() for n in self.notes])}]"
20
21    def __str__(self) -> str:
22        return self.__repr__()
23
24    def __eq__(self, other: "Scale"):
25        return (
26            isinstance(other, Scale)
27            and len(self.notes) == len(other.notes)
28            and all(x == y for x, y in zip(self.notes, other.notes))
29        )
30
31    def __ne__(self, other: "Scale"):
32        return (
33            not isinstance(other, Scale)
34            or len(self.notes) != len(other.notes)
35            or any(x != y for x, y in zip(self.notes, other.notes))
36        )
37
38    def __lshift__(self, other: int) -> "Scale":
39        if not isinstance(other, int):
40            return NotImplemented
41        return self.shifted(other)
42
43    def __rshift__(self, other: int) -> "Scale":
44        if not isinstance(other, int):
45            return NotImplemented
46        return self.shifted(-other)
47
48    def shifted(self, steps: int) -> "Scale":
49        """
50        Return a shifted version of this `Scale` essentially the same `Scale` but starting on step `steps`
51        """
52
53        if not isinstance(steps, int):
54            raise TypeError()
55
56        shifted_notes = self.notes.copy()
57
58        for _ in range(abs(steps)):
59            if steps < 0:
60                shifted_notes.insert(0, shifted_notes.pop(-1).preceding(shifted_notes[0]))
61            elif steps > 0:
62                shifted_notes.append(shifted_notes.pop(0).following(shifted_notes[-1]))
63
64        return Scale(shifted_notes)
65
66    def tonic(self) -> Note:
67        """
68        Return the tonic of the scale as a `Note`
69        """
70        return self.notes[0]

A musical Scale consisting of a list of ordered Notes

Scale(notes: list[Note])
13    def __init__(self, notes: list[Note]):
14        if not isinstance(notes, list):
15            raise TypeError()
16        self.notes = notes
notes: list[Note]

List of Notes in the Scale

def shifted(self, steps: int) -> Scale:
48    def shifted(self, steps: int) -> "Scale":
49        """
50        Return a shifted version of this `Scale` essentially the same `Scale` but starting on step `steps`
51        """
52
53        if not isinstance(steps, int):
54            raise TypeError()
55
56        shifted_notes = self.notes.copy()
57
58        for _ in range(abs(steps)):
59            if steps < 0:
60                shifted_notes.insert(0, shifted_notes.pop(-1).preceding(shifted_notes[0]))
61            elif steps > 0:
62                shifted_notes.append(shifted_notes.pop(0).following(shifted_notes[-1]))
63
64        return Scale(shifted_notes)

Return a shifted version of this Scale essentially the same Scale but starting on step steps

def tonic(self) -> Note:
66    def tonic(self) -> Note:
67        """
68        Return the tonic of the scale as a `Note`
69        """
70        return self.notes[0]

Return the tonic of the scale as a Note

OCTAVE_RATIO = [Ratio 2.0000]
SEMITONE_RATIO = [Ratio 1.0595]
C0_FREQUENCY = 16.351597831287375
SEMITONE = [Interval m2 (1.0595)]
MINOR_SECOND = [Interval m2 (1.0595)]
WHOLETONE = [Interval M2 (1.1225)]
MAJOR_SECOND = [Interval M2 (1.1225)]
MINOR_THIRD = [Interval m3 (1.1892)]
MAJOR_THIRD = [Interval M3 (1.2599)]
PERFECT_FOURTH = [Interval P4 (1.3348)]
AUGMENTED_FOURTH = [Interval A4 (1.4142)]
TRITONE = [Interval d5 (1.4142)]
DIMINISHED_FIFTH = [Interval d5 (1.4142)]
PERFECT_FIFTH = [Interval P5 (1.4983)]
MINOR_SIXTH = [Interval m6 (1.5874)]
MAJOR_SIXTH = [Interval M6 (1.6818)]
MINOR_SEVENTH = [Interval m7 (1.7818)]
MAJOR_SEVENTH = [Interval M7 (1.8877)]
OCTAVE = [Interval P8 (2.0000)]
IONIAN = [Mode P1 M2 M3 P4 P5 M6 M7]
PHRYGIAN = [Mode P1 m2 m3 P4 P5 m6 m7]
DORIAN = [Mode P1 M2 m3 P4 P5 M6 m7]
LYDIAN = [Mode P1 M2 M3 d5 P5 M6 M7]
MIXOLYDIAN = [Mode P1 M2 M3 P4 P5 M6 m7]
AEOLIAN = [Mode P1 M2 m3 P4 P5 m6 m7]
LOCRIAN = [Mode P1 m2 m3 P4 d5 m6 m7]