Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented jazz minor scale and relative key functionality on harmonic, melodic, and jazz minors #1044

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 98 additions & 5 deletions music21/scale/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@
'TERMINUS_LOW', 'TERMINUS_HIGH',
'ScaleException', 'Scale',
'AbstractScale', 'AbstractDiatonicScale', 'AbstractOctatonicScale',
'AbstractHarmonicMinorScale', 'AbstractMelodicMinorScale',
'AbstractHarmonicMinorScale', 'AbstractJazzMinorScale', 'AbstractMelodicMinorScale',
'AbstractCyclicalScale', 'AbstractOctaveRepeatingScale',
'AbstractRagAsawari', 'AbstractRagMarwa', 'AbstractWeightedHexatonicBlues',
'ConcreteScale', 'DiatonicScale', 'MajorScale',
'MinorScale', 'DorianScale', 'PhrygianScale', 'LydianScale', 'MixolydianScale',
'HypodorianScale', 'HypophrygianScale', 'HypolydianScale', 'HypomixolydianScale',
'LocrianScale', 'HypolocrianScale', 'HypoaeolianScale',
'HarmonicMinorScale', 'MelodicMinorScale',
'HarmonicMinorScale', 'JazzMinorScale', 'MelodicMinorScale',
'OctatonicScale', 'OctaveRepeatingScale', 'CyclicalScale', 'ChromaticScale',
'WholeToneScale', 'SieveScale', 'ScalaScale', 'RagAsawari',
'RagMarwa', 'WeightedHexatonicBlues',
Expand Down Expand Up @@ -847,13 +847,16 @@ class AbstractHarmonicMinorScale(AbstractScale):
A true bi-directional scale that with the augmented
second to a leading tone.

This is the only scale to use the "_alteredDegrees" property.
The harmonic and jazz minor scales are the only to
use the "_alteredDegrees" property.
'''

def __init__(self, mode=None):
super().__init__()
self.type = 'Abstract Harmonic Minor'
self.octaveDuplicating = True
self.relativeMinorDegree = 1
self.relativeMajorDegree = 3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, can you explain why this is needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this code, an error message like this is given when using the getRelativeMinor/getRelativeMajor functions on the harmonic/melodic/jazz minor scales:

AttributeError: 'AbstractHarmonicMinorScale' object has no attribute 'relativeMinorDegree'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it, any of the non-natural minors, thanks. Yes, this should work because it's a method on DiatonicScale.

Can we move these attribute assignments to buildNetwork()? That's where they seem to be in all other cases.

self.dominantDegree: int = -1
self.buildNetwork()

Expand All @@ -872,6 +875,57 @@ def buildNetwork(self):
'interval': interval.Interval('a1')
}

def getRelativeMajor(self):
return MajorScale(self.tonic)

def getRelativeMinor(self):
return MinorScale(self.tonic)
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved


class AbstractJazzMinorScale(AbstractScale):
'''
A true bi-directional scale containing the raised
6th and 7th scale degrees.

The harmonic and jazz minor scales are the only to
use the "_alteredDegrees" property.
'''

def __init__(self, mode=None):
super().__init__()
self.type = 'Abstract Jazz Minor'
self.relativeMinorDegree = 1
self.relativeMajorDegree = 3
self.octaveDuplicating = True
self.dominantDegree: int = -1
self.buildNetwork()

def buildNetwork(self):
intervalList = ['M2', 'm2', 'M2', 'M2', 'm2', 'M2', 'M2'] # a to A
self.tonicDegree = 1
self.dominantDegree = 5
self._net = intervalNetwork.IntervalNetwork(intervalList,
octaveDuplicating=self.octaveDuplicating,
pitchSimplification=None)

# raise the sixth and seventh in all directions
# 6 and 7 here are both scale step/degree, not node id
self._alteredDegrees[6] = {
'direction': intervalNetwork.DIRECTION_BI,
'interval': interval.Interval('a1')
}

self._alteredDegrees[7] = {
'direction': intervalNetwork.DIRECTION_BI,
'interval': interval.Interval('a1')
}

def getRelativeMajor(self):
return MajorScale(self.tonic)

def getRelativeMinor(self):
return MinorScale(self.tonic)
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved


class AbstractMelodicMinorScale(AbstractScale):
'''
Expand All @@ -882,6 +936,8 @@ def __init__(self, mode=None):
super().__init__()
self.type = 'Abstract Melodic Minor'
self.octaveDuplicating = True
self.relativeMinorDegree = 1
self.relativeMajorDegree = 3
self.dominantDegree: int = -1
self.buildNetwork()

Expand All @@ -893,6 +949,11 @@ def buildNetwork(self):
pitchSimplification=None)
self._net.fillMelodicMinor()

def getRelativeMajor(self):
return MajorScale(self.tonic)

def getRelativeMinor(self):
return MinorScale(self.tonic)

class AbstractCyclicalScale(AbstractScale):
'''
Expand Down Expand Up @@ -2830,9 +2891,9 @@ class HarmonicMinorScale(DiatonicScale):
'''
The harmonic minor collection, realized as a scale.

(The usage of this collection as a scale, is quite ahistorical for
(The usage of this collection as a scale is quite ahistorical for
Western European classical music, but it is common in other parts of the
world, but where the term "HarmonicMinor" would not be appropriate).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the space, but the "but" should remain

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, because "where" is not delimiting certain parts of the world where the term is/is not meaningful.

world, where the term "Harmonic Minor" would not be appropriate).

>>> sc = scale.HarmonicMinorScale('e4')
>>> [str(p) for p in sc.pitches]
Expand Down Expand Up @@ -2864,6 +2925,38 @@ def __init__(self, tonic=None):
# self._abstract.buildNetwork()


class JazzMinorScale(DiatonicScale):
'''
The jazz minor scale.
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
>>> sc = scale.JazzMinorScale('e4')
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
>>> [str(p) for p in sc.pitches]
['E4', 'F#4', 'G4', 'A4', 'B4', 'C#5', 'D#5', 'E5']
>>> sc.getTonic()
<music21.pitch.Pitch E4>
>>> sc.getDominant()
<music21.pitch.Pitch B4>
>>> sc.pitchFromDegree(1) # scale degree 1 is treated as lowest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually no need for comments in doc tests, write it as documentation just above.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<music21.pitch.Pitch B4>
>>> sc.pitchFromDegree(1) # scale degree 1 is treated as lowest
<music21.pitch.Pitch E4>
>>> sc = scale.JazzMinorScale()

explain what's happening here.
usually no need for comments in doc tests, write it as documentation just above.

To clarify, these comments are largely borrowed from other diatonic scales (mainly the harmonic minor) that I had used as a template. Should I rework the comments on these other scales as well?

<music21.pitch.Pitch E4>
>>> sc = scale.JazzMinorScale()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space above and explain what's happening here.

>>> sc.deriveRanked(['C', 'E', 'G'], comparisonAttribute='name')
[(3, <music21.scale.JazzMinorScale G jazz minor>),
(3, <music21.scale.JazzMinorScale F jazz minor>),
(2, <music21.scale.JazzMinorScale B- jazz minor>),
(2, <music21.scale.JazzMinorScale A jazz minor>)]
'''

def __init__(self, tonic=None):
super().__init__(tonic=tonic)
self.type = 'jazz minor'

# note: this changes the previously assigned AbstractDiatonicScale
# from the DiatonicScale base class

self._abstract = AbstractJazzMinorScale()
# network building happens on object creation
# self._abstract.buildNetwork()
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved


class MelodicMinorScale(DiatonicScale):
'''
A melodic minor scale, which is not the same ascending or descending
Expand Down