Improve docstrings for Sphinx documentation

This commit is contained in:
Jonathan Harker 2024-10-02 13:51:52 +13:00
parent 42d4cfb427
commit 64dd35e1f8
4 changed files with 238 additions and 82 deletions

View file

@ -28,6 +28,7 @@ release = '0.1'
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
'sphinx.ext.napoleon',
'sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode',

View file

@ -6,6 +6,8 @@ setclass module
.. automodule:: setclass.setclass
:members:
:special-members: __init__
:member-order: bysource
:undoc-members:
:show-inheritance:

View file

@ -1,3 +1,4 @@
# Main requirements
-r requirements.txt
# Code lint
@ -14,5 +15,6 @@ tox
# Documentation
sphinx
sphinx_rtd_theme
sphinx-rtd-theme
sphinxcontrib-napoleon
myst-parser

View file

@ -6,18 +6,32 @@ import re
class SetClass(list):
"""
Musical set class, containing zero or more pitch classes.
Musical set class, containing zero or more pitch classes. This implementation can handle set
classes of any arbitrary :attr:`tonality`, or number of divisions of the octave.
"""
def __init__(self, *args: int, tonality: int = 12) -> None:
"""
Instantiate a Class Set with a series of integers. Each pitch class is "normalised" by
modulo the tonality; the set is sorted in ascending order; and values "rotated" until the
lowest value is 0. For a number of divisions of the octave other than the default (Western
harmony) value of 12, supply an integer value using the 'tonality' keyword. Example:
Instantiate a :class:`ClassSet` object with a series of integers.
sc = SetClass(0, 7, 9)
sc = SetClass(0, 2, 3, tonality=7)
Each supplied pitch class is "normalised" by modulo the :attr:`tonality`, the set is
sorted in ascending order, and values are transposed (rotated) until the lowest value is 0.
For a number of divisions of the octave other than the default (Western harmony) value of
12, supply an integer value using the 'tonality' keyword. Example:
>>> sc1 = SetClass(0, 7, 9)
>>> sc1
SetClass{0,7,9}
>>> sc2 = SetClass(0, 2, 3, tonality=7)
>>> sc2
SetClass{0,2,3} (T=7)
Args:
*args (int):
The pitch class values.
tonality (int):
The :attr:`tonality`, or modulus, is the number of divisions of the octave.
If unspecified, the default value of 12 is assumed (Western harmony).
"""
self._tonality = tonality
@ -39,48 +53,60 @@ class SetClass(list):
super().append(i)
def __repr__(self) -> str:
"""Return the Python instance string representation."""
"""Return a string representation of this instance."""
s = f"SetClass{{{','.join(str(i) for i in self.pitch_classes)}}}"
return s if self.tonality == 12 else f"{s} (T={self.tonality})"
def __hash__(self) -> int:
"""Return a unique identifier for the instance, based on the :attr:`pitch_classes` used."""
return sum([hash(i) for i in self.pitch_classes])
@property
def pitch_classes(self) -> list(int):
"""The pitch classes as an ordered :class:`list` of integers."""
return list(self)
@cached_property
def tonality(self) -> int:
"""
Returns the number of (equal) divisions of the octave. The default value is 12, which
The number of (equal) divisions of the octave. The default value is 12, which
represents traditional Western chromatic harmony, the octave divided into twelve semitones.
"""
return self._tonality
@cached_property
def cardinality(self) -> int:
"""Returns the cardinality of the set class, i.e. the number of pitch classes."""
"""
The cardinality of the set class, i.e. the number of :attr:`pitch_classes`.
"""
return len(self.pitch_classes)
@cached_property
def brightness(self) -> int:
"""Returns the brightness of the set class, defined as the sum of the pitch class values."""
"""
The brightness of the set class.
Brightness (B) is a property proposed by Brian Leonard, defined as the sum of the values of
the :attr:`pitch_classes`.
"""
return sum(self.pitch_classes)
@cached_property
def decimal(self) -> int:
"""
The decimal value of the binary representation of the :attr:`pitch_classes`.
Returns the decimal value of the pitch classes expressed as a binary bit mask, i.e. the sum
of 2 where i is each pitch class value. For example, {0,1,4,6} has a binary value of
000001010011, which is the decimal value 83.
Goyette (2012) p. 25, citing Brinkman (1986).
of 2 where i is each pitch class value. For example, set class {0,1,4,6} has a binary value
of 000001010011, which is the decimal value 83.
Further reading: Goyette (2012) p. 25, citing Brinkman (1986).
"""
return sum([2**i for i in self.pitch_classes])
@cached_property
def adjacency_intervals(self) -> list(int):
"""Adjacency intervals between the pitch classes, used for Leonard notation subscripts."""
"""The ordered :class:`list` of adjacency intervals between the pitch classes."""
if not self.pitch_classes:
return list()
intervals = list()
@ -95,7 +121,14 @@ class SetClass(list):
@cached_property
def z_relations(self) -> list:
"""
Return all distinct set classes with the same interval vector (Allen Forte: "Z-related").
A :class:`list` of the Z-relations of this set class.
Allen Forte in his book *The Structure of Atonal Music* (1973) described the relationship
between twins of set classes that share the same :attr:`interval_vector`, but are not
related by :attr:`inversion`, :attr:`complement`, or transposition (rotation), as Z-related
('Z' for *zygote* from Greek: ζυγωτός, 'joined'). This property returns all distinct set
classes with the same interval vector.
For example, Forte 4-Z15 {0,1,4,6} and Forte 4-Z29 {0,1,3,7} both have iv1,1,1,1,1,1 but
are not inversions, complements, or transpositions (rotations) of each other.
"""
@ -104,40 +137,66 @@ class SetClass(list):
@cached_property
def interval_vector(self) -> list:
"""
An ordered tuple containing the multiplicities of each interval class in the set class.
Denoted in angle-brackets, e.g.
The interval vector of {0,2,4,5,7,9,11} is 2,5,4,3,6,1
Rahn (1980), p. 100
An ordered :class:`list` containing the multiplicities of each interval class in the set
class. Denoted in angle-brackets, e.g. the interval vector of {0,2,4,5,7,9,11} is
2,5,4,3,6,1. Each element in the vector is the frequency of occurrence of the interval
represented by its ordinal position, i.e. 2,5,4,3,6,1 means two semitones, five major
seconds, four minor thirds, and so on. Rahn (1980), p. 100.
"""
from itertools import combinations
iv = [0 for i in range(1, int(self.tonality / 2) + 1)]
for (a, b) in combinations(self.pitch_classes, 2):
ic = SetClass.unordered_interval(a, b)
ic = self.unordered_interval(a, b)
iv[ic - 1] += 1
return iv
def ordered_interval(a: int, b: int) -> int:
def ordered_interval(self, a: int, b: int) -> int:
"""
The ordered interval or "directed interval" (Babbitt) of two pitch classes is determined by
the difference of the pitch class values, modulo 12:
ia,b = b-a mod 12 Rahn (1980), p. 25
"""
return (b % 12 - a % 12) % 12
Return the ordered interval of two pitch classes.
def unordered_interval(a: int, b: int) -> int:
The ordered interval, or "directed interval" (Babbitt) of two :attr:`pitch_classes` is
determined by the difference of the pitch class values, modulo :attr:`tonality`. For example
with tonality 12:
ia,b = b-a mod 12
Rahn (1980), p. 25
Args:
a (int): the first pitch class value.
b (int): the second pitch class value.
Returns:
The ordered interval as ann integer.
"""
return (b % self.tonality - a % self.tonality) % self.tonality
def unordered_interval(self, a: int, b: int) -> int:
"""
Return the unordered interval of two pitch classes.
The unordered interval (also "interval distance", "interval class", "ic", or "undirected
interval") of two pitch classes is the smaller of the two possible ordered intervals
(differences in pitch class value):
i(a,b) = min: ia,b, ib,a Rahn (1980), p. 28
interval") of two :attr:`pitch_classes` is the smaller of the possible values of
:attr:`ordered_interval` (differences in pitch class value):
i(a,b) = min: ia,b, ib,a
Rahn (1980), p. 28
Args:
a (int): the first pitch class value.
b (int): the second pitch class value.
Returns:
The ordered interval as ann integer.
"""
return min(SetClass.ordered_interval(a, b), SetClass.ordered_interval(b, a))
return min(self.ordered_interval(a, b), self.ordered_interval(b, a))
@cached_property
def versions(self) -> list(SetClass):
"""
Returns all possible zero-normalised versions (clock rotations) of this set class,
sorted by brightness. See Rahn (1980) Set types, Tₙ
All possible zero-normalised transpositions (rotations) of this set class, sorted by
:attr:`brightness`. See Rahn (1980), Tₙ set types.
"""
# The empty set class has one version, itself
if not self.pitch_classes:
@ -154,9 +213,13 @@ class SetClass(list):
@cached_property
def rahn_normal_form(self) -> SetClass:
"""
Return the Rahn normal form of the set class; Leonard describes this as "most dispersed from
the right". Find the smallest outside interval, and proceed inwards from the right until one
result remains. See Rahn (1980), p. 33
The Rahn normal form of the set class.
John Rahn's normal form described in his book *Basic Atonal Theory* (1980) is an algorithm
to produce a unique form for each set class (see :attr:`rahn_normal_form`). Often wrongly
described as "most packed to the left", Leonard describes it as "most dispersed from the
right". Find the smallest outside interval, and if necessary proceed inwards from the right
finding the smallest next interval until one result remains. See Rahn (1980), p. 33.
"""
def _most_dispersed(versions, n):
return [i for i in versions if i.pitch_classes[-n] == min([i.pitch_classes[-n] for i in versions])]
@ -171,8 +234,8 @@ class SetClass(list):
@cached_property
def packed_left(self) -> SetClass:
"""
Return the form of the set class that is most packed to the left (smallest adjacency
intervals to the left). Find the smallest adjacency interval, and proceed towards the right
The form of the set class that is most packed to the left (the smallest adjacency intervals
to the left). Find the smallest first adjacency interval, and proceed towards the right
until one result remains.
"""
def _most_packed(versions, n):
@ -188,11 +251,12 @@ class SetClass(list):
@cached_property
def prime_form(self) -> SetClass:
"""
Return the prime form of the set class. Find the forms with the smallest outside interval,
and if necessary chose the form most packed to the left (the smallest adjacency intervals
working from left to right).
Allen Forte describes the algorithm in his book *The Structure of Atonal Music* (1973) in
section 1.2 (pp. 3-5), citing Milton Babbitt (1961).
Return the prime form of the set class.
Allen Forte describes the algorithm for finding this normal form in his book *The Structure
of Atonal Music* (1973) in section 1.2 (pp. 3-5), citing Milton Babbitt (1961). Find the
forms with the smallest outside interval, and if necessary chose the form most packed to the
left (the smallest adjacency intervals working from left to right).
"""
def _most_packed(versions, n):
return [i for i in versions if i.pitch_classes[n] == min([i.pitch_classes[n] for i in versions])]
@ -211,7 +275,8 @@ class SetClass(list):
@cached_property
def darkest_form(self) -> SetClass:
"""
Returns the version with the smallest brightness value, most packed to the left.
The version of this set class with the smallest :attr:`brightness` value, most packed to the
left.
"""
if not self.versions:
return self
@ -233,7 +298,7 @@ class SetClass(list):
@cached_property
def brightest_form(self) -> SetClass:
"""
Returns the version with the largest brightness value.
The version of this set class with the largest :attr:`brightness` value.
TODO: How to break a tie? Or return all matches in a tuple? Sorted by what?
"""
return self.versions[-1] if self.versions else self
@ -241,15 +306,16 @@ class SetClass(list):
@cached_property
def inversion(self) -> SetClass:
"""
Returns the inversion of this set class, equivalent of reflection through the 0 axis on a
clock diagram.
The inversion of this set class, transposed so the smallest pitch class is 0. Equivalent to
a reflection through the 0 axis on a clock diagram.
"""
return SetClass(*[self.tonality - i for i in self.pitch_classes], tonality=self.tonality)
@cached_property
def is_symmetrical(self) -> bool:
"""
Returns whether this set class is symmetrical upon inversion, for example Forte 5-Z17:
Whether this set class is symmetrical upon inversion, for example Forte 5-Z17:
{0,1,2,5,9} {0,4,8,9,11}
"""
return self.darkest_form == self.inversion.darkest_form
@ -257,34 +323,45 @@ class SetClass(list):
@cached_property
def complement(self) -> SetClass:
"""
Returns the set class containing all pitch classes absent in this one (rotated so the
smallest is 0).
The set class containing all :attr:`pitch_classes` absent in this one, transposed so the
smallest pitch class is 0.
"""
return SetClass(*[i for i in range(self.tonality) if i not in self.pitch_classes], tonality=self.tonality)
@cached_property
def dozenal_notation(self) -> str:
"""
If tonality is no greater than 12, return a string representation using Dozenal Society
characters '' for 10 and '' for 11 (the Pitman forms from Unicode 8.0 release, 2015).
A string representation using and for 10 and 11.
For a set class with a :attr:`tonality` no greater than 12, this property replaces the 10
and 11 pitch classes with the Dozenal Society characters '' and '' respectively. These are
the Pitman forms from the Unicode 8.0 specification released in 2015.
"""
return f"{self}" if self.tonality > 12 else f"{self}".replace('10', '').replace('11', '')
@cached_property
def duodecimal_notation(self) -> str:
"""
If tonality is no greater than 12, replace 10 and 11 with 'T' and 'E'.
A string representation using T and E for 10 and 11.
For a set class with a :attr:`tonality` no greater than 12, this property replaces the 10
and 11 pitch classes with the letters 'T' and 'E' respectively.
"""
return f"{self}" if self.tonality > 12 else f"{self}".replace('10', 'T').replace('11', 'E')
@cached_property
def leonard_notation(self) -> str:
"""
Returns a string representation of this set class using subscripts to denote the adjacency
intervals between the pitch classes instead of commas, and the overall brightness (sum of
pitch class values) denoted as a superscript. In standard tonality (T=12) the letters T and
E are used for pitch classes 10 and 11. For example, Forte 7-34, {0,1,3,4,6,8,10} is
notated [013468T₂]³².
The string representation proposed by Brian Leonard.
Returns a string representation of this set class using subscripts to denote the
:attr:`adjacency_intervals` between the pitch classes instead of commas, and the overall
:attr:`brightness` (sum of the values of :attr:`pitch_classes`) denoted as a superscript.
In standard tonality (T=12) the letters T and E are used for pitch classes 10 and 11. For
example, Forte 7-34 would be written as [013468T₂]³² in this scheme:
>>> SetClass.from_string('{0,1,3,4,6,8,10}').leonard_notation
[013468T₂]³²
"""
# Return numbers as subscript and superscript strings:
def subscript(n: int) -> str:
@ -307,17 +384,49 @@ class SetClass(list):
@classmethod
def from_string(this, string: str, tonality: int = 12) -> SetClass:
"""
Attempt to create a SetClass from any string containing a sequence of zero or more integers.
A useful convenience function, e.g. SetClass.from_string('{0,3,7,9}')
Create a :class:`SetClass` from a string.
A useful convenience function for converting from various text representations of set
classes that contain a sequence of zero or more integers. Non-integer content is ignored,
and integers must be separated by some non-integer character(s), usually a space, comma, or
similar. For instance:
>>> SetClass.from_string("Prélude à l'après-midi d'un faune uses [0,2,4,6,8,10]")
SetClass{0,2,4,6,8,10}
>>> SetClass.from_string('{0, 3, 7}', tonality=8)
SetClass{0,3,7} (T=8)
Args:
string (str):
Any string representation of a set class, containing some integer content.
tonality (int):
The :attr:`tonality`, or modulus, is the number of divisions of the octave.
If unspecified, the default value of 12 is assumed (Western harmony).
Returns:
A :class:`SetClass` instance with the pitch classes found in the string.
"""
return SetClass(*re.findall(r'\d+', string), tonality=tonality)
@classmethod
def all_of_cardinality(cls, cardinality: int, tonality: int = 12) -> set:
def all_of_cardinality(this, cardinality: int, tonality: int = 12) -> set:
"""
Returns all set classes of a given cardinality. Tonality can be specified, default is 12.
Warning: high values of tonality can take a long time to calculate (T=24 takes about a
Returns a :class:`set` of all possible :class:`SetClass` objects with a given
:attr:`cardinality`.
.. warning:: High values of tonality can take a long time to calculate (T=24 takes about a
minute on an Intel i7-13700H CPU).
Args:
cardinality (int):
The :attr:`cardinality` of the set classes to return.
tonality (int):
The :attr:`tonality`, or modulus, is the number of divisions of the octave.
If unspecified, the default value of 12 is assumed (Western harmony).
Returns:
A :class:`set` of :class:`SetClass` objects.
"""
def _powerset(seq):
"""Set of all possible unique subsets."""
@ -330,32 +439,68 @@ class SetClass(list):
return set(SetClass(*i, tonality=tonality) for i in _powerset(range(tonality)) if len(i) == cardinality and 0 in i)
@classmethod
def darkest_of_cardinality(this, cardinality: int, tonality: int = 12, prime: bool = False) -> set:
def darkest_of_cardinality(this, cardinality: int, tonality: int = 12) -> set:
"""
Returns all set classes of a given cardinality, in their darkest forms (ignore rotations).
Tonality can be specified, default is 12.
Warning: high values of tonality can take a long time to calculate (T=24 takes about a
Returns a :class:`set` of all :class:`SetClass` objects with a given :attr:`cardinality` in
their darkest forms.
This produces a smaller set than :attr:`all_of_cardinality`, since it eliminates set classes
that are "brighter" transpositions (rotations) of the darkest :class:`SetClass` returned by
the :attr:`darkest_form` property.
.. warning:: High values of tonality can take a long time to calculate (T=24 takes about a
minute on an Intel i7-13700H CPU).
Args:
cardinality (int):
The :attr:`cardinality` of the set classes to return.
tonality (int):
The :attr:`tonality`, or modulus, is the number of divisions of the octave.
If unspecified, the default value of 12 is assumed (Western harmony).
Returns:
A :class:`set` of :class:`SetClass` objects in :attr:`darkest_form`.
"""
return set(i.darkest_form for i in this.all_of_cardinality(cardinality, tonality))
@classmethod
def normal_of_cardinality(this, cardinality: int, tonality: int = 12, prime: bool = False) -> set:
def normal_of_cardinality(this, cardinality: int, tonality: int = 12) -> set:
"""
Returns all set classes of a given cardinality, in their (darkest) Rahn normal forms (ignore
rotations). Tonality can be specified, default is 12.
Warning: high values of tonality can take a long time to calculate (T=24 takes about a
Returns a :class:`set` of all :class:`SetClass` objects with a given :attr:`cardinality` in
their Rahn normal form.
This produces a smaller set than :attr:`all_of_cardinality`, since it eliminates set classes
that are transpositions (rotations) of the normalised :class:`SetClass` returned by the
:attr:`rahn_normal_form` property.
.. warning:: High values of tonality can take a long time to calculate (T=24 takes about a
minute on an Intel i7-13700H CPU).
Args:
cardinality (int):
The :attr:`cardinality` of the set classes to return.
tonality (int):
The :attr:`tonality`, or modulus, is the number of divisions of the octave.
If unspecified, the default value of 12 is assumed (Western harmony).
Returns:
A :class:`set` of :class:`SetClass` objects in :attr:`rahn_normal_form`.
"""
return set(i.rahn_normal_form for i in this.all_of_cardinality(cardinality, tonality))
@classmethod
def bright_rahn_normal_forms(this) -> set:
"""
The set of Rahn normal forms that are not the same as the darkest form.
John Rahn's normal form from *Basic Atonal Theory* (1980) is an algorithm to produce a
unique form for each set class. Most of the time it is also the "darkest" (smallest
brightness value), except for all the times when that is not the case; this function returns
that list, as a set of (cardinality, darkest, rahn) tuples.
unique form for each set class (see :attr:`rahn_normal_form`). Most of the time it is also
the same as the :attr:`darkest_form` (that with the smallest :attr:`brightness` value),
except for all the times when that is not the case!
Returns:
A :class:`set` of (:attr:`cardinality`, :attr:`darkest_form`, :attr:`rahn_normal_form`)
tuples.
"""
cases = set()
for C in range(13): # 0 to 12
@ -367,12 +512,18 @@ class SetClass(list):
@classmethod
def bright_prime_forms(this) -> set:
"""
The set of Forte prime forms that are not the same as the darkest form.
Allen Forte describes the algorithm for deriving the "prime" or zeroed normal form, in his
book *The Structure of Atonal Music* (1973) in section 1.2 (pp. 3-5), citing Milton Babbitt
(1961). It produces a unique form for each set class, which most of the time is also the
"darkest" (smallest brightness value), except for all the times when that is not the case;
this function returns that list, as a set of (cardinality, darkest, prime) tuples.
(1961). It produces a unique form for each set class (see :attr:`prime_form`), which most of
the time is the same as the :attr:`darkest_form` (that with the smallest :attr:`brightness`
value), except for all the times when that is not the case!
Returns:
A :class:`set` of (:attr:`cardinality`, :attr:`darkest_form`, :attr:`prime_form`) tuples.
"""
cases = set()
for C in range(13): # 0 to 12
for sc in this.all_of_cardinality(C):