Scale

Stack of intervals

The Scale object is a collection of intervals and agnostic to any key. From the basic scale, notes can be derived by providing a root note.


source

Scale

 Scale (name:str)

Initialize self. See help(type(self)) for accurate signature.

Initialization

The Scale representation gives you the name and intervals of the scale.

major = Scale("major")
major
Scale: Major. Intervals: ['1', '2', '3', '4', '5', '6', '7']
dorian = Scale("dorian")
dorian
Scale: Dorian. Intervals: ['1', '2', 'b3', '4', '5', '6', 'b7']
lydian = Scale("lydian")
lydian
Scale: Lydian. Intervals: ['1', '2', '3', '#4', '5', '6', '7']

Properties

The Scale object stores how many flats, sharps and naturals are in the key.

For example, a major scale contains only naturals. No flats or sharps.

major.naturals, major.sharps, major.flats
(7, 0, 0)

A dorian scale has 2 flats (the 3rd and 7th degrees).

dorian.flats
2

A lydian scale has 1 sharp (the 4th degree).

lydian.sharps
1

Listify the scale to get the intervals or call .intervals.

list(lydian)
['1', '2', '3', '#4', '5', '6', '7']

Semitones for the invervals in a scale can be retrieved in an absolute (intervals between notes) or relative (intervals from root).

assert major.rel_semitones == [0, 2, 4, 5, 7, 9, 11]
major.rel_semitones
[0, 2, 4, 5, 7, 9, 11]
assert major.abs_semitones == [2, 2, 1, 2, 2, 2, 1]
assert sum(major.abs_semitones) == 12
major.abs_semitones
[2, 2, 1, 2, 2, 2, 1]

Comparison

Scales can be compared to each other.

assert Scale("minor") == Scale("aeolian") == Scale("natural minor")
assert not major == dorian
assert lydian != dorian
assert lydian == lydian
assert Scale("minor") == Scale("aeolian")

Custom Scales

Almost all scales are available to initialize by name, but custom scales can be created with from_intervals. Consult Scale.available_scales to get the built-in scales.

For example, we can create a custom persian scale.

persian = Scale.from_intervals(["1", "b2", "3", "4", "b5", "b6", "7"], "persian")
persian
Scale: Persian. Intervals: ['1', 'b2', '3', '4', 'b5', 'b6', '7']

If no name is given, Scale will try to infer the name.

mel_minor = Scale.from_intervals(["1", "2", "b3", "4", "5", "6", "7"])
assert mel_minor.name == "melodic minor"
mel_minor
Scale: Melodic Minor. Intervals: ['1', '2', 'b3', '4', '5', '6', '7']

Note Generation

From a Scale object, we can get the notes given a root note.


source

Scale.get_notes

 Scale.get_notes (root, oct=4)

Get the notes of a scale from a root note.

c_major = Scale("major").get_notes("C")
assert [str(n) for n in c_major] == ['C', 'D', 'E', 'F', 'G', 'A', 'B']
c_major
[C4, D4, E4, F4, G4, A4, B4]
g_major = Scale("major").get_notes("G")
g_major
[G4, A4, B4, C5, D5, E5, F#5]
c_dorian = Scale("dorian").get_notes("C")
assert [str(n) for n in c_dorian] == ['C', 'D', 'D#', 'F', 'G', 'A', 'A#']
c_dorian
[C4, D4, D#4, F4, G4, A4, A#4]

Exhaustive Diatonic Chords

We can get all diatonic chords in the scale with get_diatonic_chords.


source

Scale.get_diatonic_chords

 Scale.get_diatonic_chords (root, min_notes=3)

For example, let’s look at all the diatonic E chords in the C major scale we can identify.

[c for c in Scale("major").get_diatonic_chords("C") if "E" in c.name]
[Chord: 'E minor seventh, second inversion'. Notes: ['D4', 'E4', 'G4'],
 Chord: 'E minor seventh, second inversion'. Notes: ['D4', 'E4', 'B4'],
 Chord: 'E minor triad'. Notes: ['E4', 'G4', 'B4'],
 Chord: 'E suspended fourth triad'. Notes: ['E4', 'A4', 'B4'],
 Chord: 'Esus4|CM6'. Notes: ['C4', 'E4', 'A4', 'B4'],
 Chord: 'E suspended seventh, third inversion'. Notes: ['D4', 'E4', 'A4', 'B4']]

Interval Names

You can easily get relative and absolute interval names. Both shorthand and full names are available.


source

Scale.abs_interval_names

 Scale.abs_interval_names (short=False)

source

Scale.rel_interval_names

 Scale.rel_interval_names (short=False)
assert major.rel_interval_names() == ['major second', 'major third', 'perfect fourth', 'perfect fifth', 'major sixth', 'major seventh']
assert major.rel_interval_names(short=True) == ['1', '2', '3', '4', '5', '6', '7']
assert major.abs_interval_names() == ['major second', 'major second', 'minor second', 'major second', 'major second', 'major second', 'minor second']
assert major.abs_interval_names(short=True) == ['2', '2', 'b2', '2', '2', '2', 'b2']
major.rel_interval_names()
['major second',
 'major third',
 'perfect fourth',
 'perfect fifth',
 'major sixth',
 'major seventh']
Scale("locrian").rel_interval_names()
['minor second',
 'minor third',
 'perfect fourth',
 'diminished fifth',
 'minor sixth',
 'minor seventh']

Alternative Scale Names

Many scales have alternative names. We can derive them using get_scale_names.


source

Scale.get_scale_names

 Scale.get_scale_names ()
assert major.get_scale_names() == ["ionian", "major"]
major.get_scale_names()
['ionian', 'major']

Modes of scale

A mode is a scale derived from a note on the scale. We can build a new Scale on every step of a Scale by cycling through the modes.

For this to work, we first need to shift the semitones of our scale and get a new set of relative semitones.

assert major.abs_semitones == [2, 2, 1, 2, 2, 2, 1]
assert major._shift_abs_semitones(1) == [2, 1, 2, 2, 2, 1, 2]
# Semitones for dorian scale (shift semitones 1 to the left)
major._shift_abs_semitones(1)
[2, 1, 2, 2, 2, 1, 2]

When we accumulate the absolute semitones of a scale, we get the relative semitones.

assert major.rel_semitones == [0, 2, 4, 5, 7, 9, 11]
assert major._shift_rel_semitones(1) == [0, 2, 3, 5, 7, 9, 10]
# Relative semitones for dorian scale
major._shift_rel_semitones(1)
[0, 2, 3, 5, 7, 9, 10]

We can now reconstruct the intervals from the relative semitones.


source

semi_to_intvals

 semi_to_intvals (semitones)

Convert relative semitone values to interval names.

dorian_intervals = semi_to_intvals(major._shift_rel_semitones(1))
assert dorian_intervals == ['1', '2', 'b3', '4', '5', '6', 'b7']
dorian_intervals
['1', '2', 'b3', '4', '5', '6', 'b7']

Now let’s reconstruct a new Scale object from these intervals.

dorian = Scale.from_intervals(dorian_intervals)
assert dorian.name == "dorian"
dorian
Scale: Dorian. Intervals: ['1', '2', 'b3', '4', '5', '6', 'b7']

Now we can create a method to retrieve all modes for any given Scale.


source

Scale.get_modes

 Scale.get_modes ()

source

Scale.shift_intvals

 Scale.shift_intvals (n:int)

Shift the intervals of a scale by n steps.

The most well known modes are the modes of the major scale, which are: 1. Ionian (Major) 2. Dorian 3. Phrygian 4. Lydian 5. Mixolydian 6. Aeolian (Natural Minor) 7. Locrian

maj_modes = major.get_modes()
assert [m.name for m in maj_modes] == ["ionian", "dorian", "phrygian", "lydian", "mixolydian", "minor", "locrian"]
maj_modes
[Scale: Ionian. Intervals: ['1', '2', '3', '4', '5', '6', '7'],
 Scale: Dorian. Intervals: ['1', '2', 'b3', '4', '5', '6', 'b7'],
 Scale: Phrygian. Intervals: ['1', 'b2', 'b3', '4', '5', 'b6', 'b7'],
 Scale: Lydian. Intervals: ['1', '2', '3', '#4', '5', '6', '7'],
 Scale: Mixolydian. Intervals: ['1', '2', '3', '4', '5', '6', 'b7'],
 Scale: Minor. Intervals: ['1', '2', 'b3', '4', '5', 'b6', 'b7'],
 Scale: Locrian. Intervals: ['1', 'b2', 'b3', '4', 'b5', 'b6', 'b7']]

If we take the modes of one of these modes we get the same modes in a different order. For example, the phrygian modes are: 1. Phrygian 2. Lydian 3. Mixolydian 4. Aeolian (Natural Minor) 5. Locrian 6. Ionian (Major)

phrygian = maj_modes[2]
phrygian_modes = phrygian.get_modes()
assert [m.name for m in phrygian_modes] == ["phrygian", "lydian", "mixolydian", "minor", "locrian", "ionian", "dorian"]
phrygian_modes
[Scale: Phrygian. Intervals: ['1', 'b2', 'b3', '4', '5', 'b6', 'b7'],
 Scale: Lydian. Intervals: ['1', '2', '3', '#4', '5', '6', '7'],
 Scale: Mixolydian. Intervals: ['1', '2', '3', '4', '5', '6', 'b7'],
 Scale: Minor. Intervals: ['1', '2', 'b3', '4', '5', 'b6', 'b7'],
 Scale: Locrian. Intervals: ['1', 'b2', 'b3', '4', 'b5', 'b6', 'b7'],
 Scale: Ionian. Intervals: ['1', '2', '3', '4', '5', '6', '7'],
 Scale: Dorian. Intervals: ['1', '2', 'b3', '4', '5', '6', 'b7']]

Let’s take a more complicated example, like the Double Harmonic Major scale. The modes of this scale are: 1. Double Harmonic Major 2. Lydian with #2 and #6 3. Ultraphrygian 4. Hungarian Minor 5. Oriental 6. Ionian with #2 and #5 (i.e. aug#2) 7. Locrian with bb3 and bb7

dhm = Scale("double harmonic major")
dhm
Scale: Double Harmonic Major. Intervals: ['1', 'b2', '3', '4', '5', 'b6', '7']
dhm_modes = dhm.get_modes()
assert [m.name for m in dhm_modes] == ["double harmonic major", "lydian #2#6", "ultraphrygian", "hungarian minor", "oriental", "ionian aug#2", "locrian bb3bb7"]
dhm_modes
[Scale: Double Harmonic Major. Intervals: ['1', 'b2', '3', '4', '5', 'b6', '7'],
 Scale: Lydian #2#6. Intervals: ['1', '#2', '3', '#4', '5', '#6', '7'],
 Scale: Ultraphrygian. Intervals: ['1', 'b2', 'b3', 'b4', '5', 'b6', '6'],
 Scale: Hungarian Minor. Intervals: ['1', '2', 'b3', '#4', '5', 'b6', '7'],
 Scale: Oriental. Intervals: ['1', 'b2', '3', '4', 'b5', '6', 'b7'],
 Scale: Ionian Aug#2. Intervals: ['1', '#2', '3', '4', '#5', '6', '7'],
 Scale: Locrian Bb3Bb7. Intervals: ['1', 'b2', '2', '4', 'b5', 'b6', '6']]

Scale Audio

Like with Note and Chord objects, Scale objects can be played in any given key with the play method.


source

Scale.play

 Scale.play (root, oct=4, length=0.3)

source

Scale.get_audio_array

 Scale.get_audio_array (root, oct=4, length=0.3)
major.play("C")
persian.play("C")

length and oct can be customized for the play method.

Scale("altered").play("C", length=0.2, oct=3)
Scale("aeolian dominant").play("C", length=0.2, oct=3)

Triads

We can derive all triads in the Scale.

Derive Triads


source

Scale.get_triads

 Scale.get_triads (root)

Get all triads in scale starting from root note.

major.get_triads("C")
[Chord: 'C major triad'. Notes: ['C4', 'E4', 'G4'],
 Chord: 'D minor triad'. Notes: ['D4', 'F4', 'A4'],
 Chord: 'E minor triad'. Notes: ['E4', 'G4', 'B4'],
 Chord: 'F major triad'. Notes: ['F4', 'A4', 'C5'],
 Chord: 'G major triad'. Notes: ['G4', 'B4', 'D5'],
 Chord: 'A minor triad'. Notes: ['A4', 'C5', 'E5'],
 Chord: 'B diminished triad'. Notes: ['B4', 'D5', 'F5']]
Scale("phrygian").get_triads("D")
[Chord: 'D minor triad'. Notes: ['D4', 'F4', 'A4'],
 Chord: 'No chord found.'. Notes: ['D#4', 'G4', 'A#4'],
 Chord: 'F major triad'. Notes: ['F4', 'A4', 'C4'],
 Chord: 'No chord found.'. Notes: ['G4', 'A#4', 'D5'],
 Chord: 'No chord found.'. Notes: ['A4', 'C4', 'D#5'],
 Chord: 'No chord found.'. Notes: ['A#4', 'D5', 'F5'],
 Chord: 'No chord found.'. Notes: ['C5', 'D#6', 'G6']]

Audio Triads

All triads in a given Scale can be played.


source

Scale.play_triads

 Scale.play_triads (root)

Play all triads in scale starting from root note.

major.play_triads("C")

Secondary Dominants

Now that we can get all the triads, we can also get all the secondary dominants within a given Scale. These are V7 chords based on each Chord in the Scale.


source

Scale.secondary_dominants

 Scale.secondary_dominants (root:str)

We expect that the secondary dominant roots are all a fifth above the underlying root and that all chords are dominant seventh chords.

normal_roots = [n.root for n in major.get_triads("C")]
sec_dom_roots = [n.root for n in major.secondary_dominants("C")]
assert all([n == c.P5() for n, c in zip(sec_dom_roots, normal_roots)])
major.secondary_dominants("C")
[Chord: 'G dominant seventh'. Notes: ['G4', 'B4', 'D5', 'F5'],
 Chord: 'A dominant seventh'. Notes: ['A4', 'C#5', 'E5', 'G5'],
 Chord: 'B dominant seventh'. Notes: ['B4', 'D#5', 'F#5', 'A5'],
 Chord: 'No chord found.'. Notes: ['C5', 'E5', 'G5', 'A#5'],
 Chord: 'D dominant seventh'. Notes: ['D5', 'F#5', 'A5', 'C6'],
 Chord: 'E dominant seventh'. Notes: ['E5', 'G#5', 'B5', 'D6'],
 Chord: 'F# dominant seventh'. Notes: ['F#5', 'A#5', 'C#6', 'E6']]

Scale Seventh Chords

Deriving Seventh Chords

We can also get all seventh chords in the Scale.


source

Scale.get_sevenths

 Scale.get_sevenths (root)

Get all seventh chords in scale starting from root note.

c_seventh_triads = major.get_sevenths("C")
c_seventh_triads
[Chord: 'C major seventh'. Notes: ['C4', 'E4', 'G4', 'B4'],
 Chord: 'D minor seventh'. Notes: ['D4', 'F4', 'A4', 'C5'],
 Chord: 'E minor seventh'. Notes: ['E4', 'G4', 'B4', 'D5'],
 Chord: 'F major seventh'. Notes: ['F4', 'A4', 'C5', 'E5'],
 Chord: 'G dominant seventh'. Notes: ['G4', 'B4', 'D5', 'F5'],
 Chord: 'A minor seventh'. Notes: ['A4', 'C5', 'E5', 'G5'],
 Chord: 'B half diminished seventh'. Notes: ['B4', 'D5', 'F5', 'A5']]

Audio Seventh Chords


source

Scale.play_sevenths

 Scale.play_sevenths (root)

Play all seventh chords in scale starting from root note.

major.play_sevenths("C")
lydian.get_sevenths("D")
[Chord: 'D major seventh'. Notes: ['D4', 'F#4', 'A4', 'C#4'],
 Chord: 'E dominant seventh'. Notes: ['E4', 'G#4', 'B4', 'D5'],
 Chord: 'F# minor seventh'. Notes: ['F#4', 'A4', 'C#4', 'E5'],
 Chord: 'G# half diminished seventh'. Notes: ['G#4', 'B4', 'D5', 'F#5'],
 Chord: 'A major seventh'. Notes: ['A4', 'C#4', 'E5', 'G#5'],
 Chord: 'B minor seventh'. Notes: ['B4', 'D5', 'F#5', 'A5'],
 Chord: 'C# minor seventh'. Notes: ['C#5', 'E6', 'G#6', 'B6']]

Scale Table

We can display all relevant information from the scale in a Pandas DataFrame table.


source

Scale.to_frame

 Scale.to_frame (root=None)

The to_frame method can be called without a root note, but is most informative when given a root note. In that case it shows additional information such as the triads and seventh chords.

major.to_frame(root="D")
Degree Relative Interval Mode Relative Semitones Absolute Semitones Notes Triad Seventh Chord
0 1 unison ionian 0 2 D D major triad D major seventh
1 2 major second dorian 2 2 E E minor triad E minor seventh
2 3 major third phrygian 4 1 F# F# minor triad F# minor seventh
3 4 perfect fourth lydian 5 2 G G major triad G major seventh
4 5 perfect fifth mixolydian 7 2 A A major triad A dominant seventh
5 6 major sixth minor 9 2 B B minor triad B minor seventh
6 7 major seventh locrian 11 1 C# C# diminished triad C# half diminished seventh

TODO: Comparison method for scales