music21.scale.intervalNetwork

An IntervalNetwork defines a scale or harmonic unit as a (weighted) digraph, or directed graph, where pitches are nodes and intervals are edges. Nodes, however, are not stored; instead, an ordered list of edges (Intervals) is provided as an archetype of adjacent nodes.

IntervalNetworks are unlike conventional graphs in that each graph must define a low and high terminus. These points are used to create a cyclic graph and are treated as point of cyclical overlap.

IntervalNetwork permits the definition of conventional octave repeating scales or harmonies (abstract chords), non-octave repeating scales and chords, and ordered interval sequences that might move in multiple directions.

A scale or harmony may be composed of one or more IntervalNetwork objects.

Both nodes and edges can be weighted to suggest tonics, dominants, finals, or other attributes of the network.

IntervalNetwork

class music21.scale.intervalNetwork.IntervalNetwork(edgeList=None, octaveDuplicating=False, deterministic=True, pitchSimplification='maxAccidental')

A graph of undefined Pitch nodes connected by a defined, ordered list of Interval objects as edges.

An octaveDuplicating boolean, if defined, can be used to optimize pitch realization routines.

The deterministic boolean, if defined, can be used to declare that there is no probabilistic or multi-pathway segments of this network.

The pitchSimplification method specifies how to simplify the pitches if they spiral out into double and triple sharps, etc. The default is ‘maxAccidental’ which specifies that each note can have at most one accidental; double-flats and sharps are not allowed. The other choices are ‘simplifyEnharmonic’ (which also converts C-, F-, B#, and E# to B, E, C, and F respectively, see simplifyEnharmonic()), ‘mostCommon’ (which adds to simplifyEnharmonic the requirement that the most common accidential forms be used, so A# becomes B-, G- becomes F#, etc. the only ambiguity allowed is that both G# and A- are acceptable), and None (or ‘none’) which does not do any simplification.

IntervalNetwork read-only properties

IntervalNetwork.degreeMax

Return the largest degree value.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.degreeMax    # returns eight, as this is the last node
8
IntervalNetwork.degreeMaxUnique

Return the largest degree value that represents a pitch level that is not a terminus of the scale.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.degreeMaxUnique
7
IntervalNetwork.degreeMin

Return the lowest degree value.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.degreeMin
1
IntervalNetwork.terminusHighNodes

Return a list of last Nodes, or Nodes that contain “terminusHigh”.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.terminusHighNodes
[<music21.scale.intervalNetwork.Node id='terminusHigh'>]
IntervalNetwork.terminusLowNodes

Return a list of first Nodes, or Nodes that contain “terminusLow”.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.terminusLowNodes
[<music21.scale.intervalNetwork.Node id='terminusLow'>]

IntervalNetwork methods

IntervalNetwork.clear()

Remove and reset all Nodes and Edges.

IntervalNetwork.degreeModulus(degree)

Return the degree modulus degreeMax - degreeMin.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.degreeModulus(3)
3
>>> net.degreeModulus(8)
1
>>> net.degreeModulus(9)
2
>>> net.degreeModulus(0)
7
IntervalNetwork.fillArbitrary(nodes, edges)

Fill any arbitrary network given node and edge definitions.

Nodes must be defined by a dictionary of id and degree values. There must be a terminusLow and terminusHigh id as string:

nodes = ({'id':'terminusLow', 'degree':1},
         {'id':0, 'degree':2},
         {'id':'terminusHigh', 'degree':3},
        )

Edges must be defined by a dictionary of Interval strings and connections. Id values will be automatically assigned. Each connection must define direction and pairs of valid node ids:

edges = ({'interval':'m2', connections:(
                ['terminusLow', 0, 'bi'],
            )},
        {'interval':'M3', connections:(
                [0, 'terminusHigh', 'bi'],
            )},
        )
>>> nodes = ({'id':'terminusLow', 'degree':1},
...          {'id':0, 'degree':2},
...          {'id':'terminusHigh', 'degree':3})
>>> edges = ({'interval':'m2', 'connections':(['terminusLow', 0, 'bi'],)},
...          {'interval':'M3', 'connections':([0, 'terminusHigh', 'bi'],)},)
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillArbitrary(nodes, edges)
>>> net.realizePitch('c4', 1)
[<music21.pitch.Pitch C4>, <music21.pitch.Pitch D-4>, <music21.pitch.Pitch F4>]
IntervalNetwork.fillBiDirectedEdges(edgeList)

Given an ordered list of bi-directed edges given as Interval specifications, create and define appropriate Nodes. This assumes that all edges are bidirected and all all edges are in order.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> [str(p) for p in net.realizePitch('g4')]
['G4', 'A4', 'B4', 'C5', 'D5', 'E5', 'F#5', 'G5']
>>> net.degreeMin, net.degreeMax
(1, 8)

Using another fill method creates a new network

>>> net.fillBiDirectedEdges(['M3', 'M3', 'M3'])
>>> [str(p) for p in net.realizePitch('g4')]
['G4', 'B4', 'D#5', 'G5']
>>> net.degreeMin, net.degreeMax
(1, 4)
>>> net.fillBiDirectedEdges([interval.Interval('M3'),
...                          interval.Interval('M3'),
...                          interval.Interval('M3')])
>>> [str(p) for p in net.realizePitch('c2')]
['C2', 'E2', 'G#2', 'B#2']
IntervalNetwork.fillDirectedEdges(ascendingEdgeList, descendingEdgeList)

Given two lists of edges, one for ascending Interval objects and another for descending, construct appropriate Nodes and Edges.

Note that the descending Interval objects should be given in ascending form.

IntervalNetwork.fillMelodicMinor()

A convenience routine for testing a complex, bi-directional scale.

>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillMelodicMinor()
>>> [str(p) for p in net.realizePitch('c4')]
['C4', 'D4', 'E-4', 'F4', 'G4', 'A4', 'B4', 'C5']
IntervalNetwork.filterPitchList(pitchTarget)

Given a list or one pitch, check if all are pitch objects; convert if necessary.

>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.filterPitchList('c#')
([<music21.pitch.Pitch C#>],
 <music21.pitch.Pitch C#>,
 <music21.pitch.Pitch C#>)
>>> net.filterPitchList(['c#4', 'f5', 'd3'])
([<music21.pitch.Pitch C#4>, <music21.pitch.Pitch F5>, <music21.pitch.Pitch D3>],
 <music21.pitch.Pitch D3>,
 <music21.pitch.Pitch F5>)
IntervalNetwork.find(pitchTarget, resultsReturned=4, comparisonAttribute='pitchClass', alteredDegrees=None)

Given a collection of pitches, test all transpositions of a realized version of this network, and return the number of matches in each for each pitch assigned to the first node.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)

a network built on G or D as

>>> net.find(['g', 'a', 'b', 'd', 'f#'])
[(5, <music21.pitch.Pitch G>), (5, <music21.pitch.Pitch D>),
 (4, <music21.pitch.Pitch A>), (4, <music21.pitch.Pitch C>)]
>>> net.find(['g', 'a', 'b', 'c', 'd', 'e', 'f#'])
[(7, <music21.pitch.Pitch G>), (6, <music21.pitch.Pitch D>),
 (6, <music21.pitch.Pitch C>), (5, <music21.pitch.Pitch A>)]

If resultsReturned is None then return every such scale.

IntervalNetwork.findMissing(pitchReference, nodeId, pitchTarget, comparisonAttribute='pitchClass', minPitch=None, maxPitch=None, direction='ascending', alteredDegrees=None)

Find all pitches in the realized scale that are not in the pitch target network based on the comparison attribute.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> [str(p) for p in net.realizePitch('G3')]
['G3', 'A3', 'B3', 'C4', 'D4', 'E4', 'F#4', 'G4']
>>> net.findMissing('g', 1, ['g', 'a', 'b', 'd', 'f#'])
[<music21.pitch.Pitch C5>, <music21.pitch.Pitch E5>]
IntervalNetwork.getNeighborNodeIds(pitchReference, nodeName, pitchTarget, direction='ascending', alteredDegrees=None)

Given a reference pitch assigned to a node id, determine the node ids that neighbor this pitch.

Returns None if an exact match.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> net.getNeighborNodeIds('c4', 1, 'b-')
(4, 5)
>>> net.getNeighborNodeIds('c4', 1, 'b')
(5, 'terminusHigh')
IntervalNetwork.getNetworkxGraph()

Create a networx graph from the raw Node representation.

Return a networks Graph object representing a realized version of this IntervalNetwork if networkx is installed

IntervalNetwork.getNext(nodeStart, direction)

Given a Node, get two lists, one of next Edges, and one of next Nodes, searching all Edges to find all matches.

There may be more than one possibility. If so, the caller must look at the Edges and determine which to use

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.nodeNameToNodes(1)[0]
<music21.scale.intervalNetwork.Node id='terminusLow'>
IntervalNetwork.getNodeDegreeDictionary(direction=None, equateTermini=True)

Return a dictionary of node id, node degree pairs. The same degree may be given for each node

There may not be unambiguous way to determine degree. Or, a degree may have different meanings when ascending or descending.

If equateTermini is True, the terminals will be given the same degree.

IntervalNetwork.getPitchFromNodeDegree(pitchReference, nodeName, nodeDegreeTarget, direction='ascending', minPitch=None, maxPitch=None, alteredDegrees=None, equateTermini=True)

Given a reference pitch assigned to node id, determine the pitch for the target node degree.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> [str(p) for p in net.realizePitch(pitch.Pitch('e-2')) ]
['E-2', 'F2', 'G2', 'A-2', 'B-2', 'C3', 'D3', 'E-3']
>>> net.getPitchFromNodeDegree('e4', 1, 1)
<music21.pitch.Pitch E4>
>>> net.getPitchFromNodeDegree('e4', 1, 7) # seventh scale degree
<music21.pitch.Pitch D#5>
>>> net.getPitchFromNodeDegree('e4', 1, 8)
<music21.pitch.Pitch E4>
>>> net.getPitchFromNodeDegree('e4', 1, 9)
<music21.pitch.Pitch F#4>
>>> net.getPitchFromNodeDegree('e4', 1, 3, minPitch='c2', maxPitch='c3')
<music21.pitch.Pitch G#2>

This will always get the lowest pitch:

>>> net.getPitchFromNodeDegree('e4', 1, 3, minPitch='c2', maxPitch='c10')
<music21.pitch.Pitch G#2>
>>> net.fillMelodicMinor()
>>> net.getPitchFromNodeDegree('c', 1, 5)
<music21.pitch.Pitch G4>
>>> net.getPitchFromNodeDegree('c', 1, 6, 'ascending')
<music21.pitch.Pitch A4>
>>> net.getPitchFromNodeDegree('c', 1, 6, 'descending')
<music21.pitch.Pitch A-4>
IntervalNetwork.getRelativeNodeDegree(pitchReference, nodeName, pitchTarget, comparisonAttribute='ps', direction='ascending', alteredDegrees=None)

Given a reference pitch assigned to node id, determine the relative node degree of pitchTarget, even if displaced over multiple octaves

Comparison Attribute determines what will be used to determine equality. Use ps (default) for post-tonal uses. name for tonal, and step for diatonic.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> [str(p) for p in net.realizePitch(pitch.Pitch('e-2')) ]
['E-2', 'F2', 'G2', 'A-2', 'B-2', 'C3', 'D3', 'E-3']
>>> net.getRelativeNodeDegree('e-2', 1, 'd3') # if e- is tonic, what is d3
7

For an octave repeating network, the neither pitch’s octave matters:

>>> net.getRelativeNodeDegree('e-', 1, 'd5') # if e- is tonic, what is d3
7
>>> net.getRelativeNodeDegree('e-2', 1, 'd') # if e- is tonic, what is d3
7
>>> net.getRelativeNodeDegree('e3', 1, 'd5') is None
True
>>> net.getRelativeNodeDegree('e3', 1, 'd5', comparisonAttribute='step')
7
>>> net.getRelativeNodeDegree('e3', 1, 'd', comparisonAttribute='step')
7
>>> net.getRelativeNodeDegree('e-3', 1, 'b-3')
5
>>> net.getRelativeNodeDegree('e-3', 1, 'e-5')
1
>>> net.getRelativeNodeDegree('e-2', 1, 'f3')
2
>>> net.getRelativeNodeDegree('e-3', 1, 'b6') is None
True
>>> net.getRelativeNodeDegree('e-3', 1, 'e-2')
1
>>> net.getRelativeNodeDegree('e-3', 1, 'd3')
7
>>> net.getRelativeNodeDegree('e-3', 1, 'e-3')
1
>>> net.getRelativeNodeDegree('e-3', 1, 'b-1')
5
>>> edgeList = ['p4', 'p4', 'p4'] # a non octave-repeating scale
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> [str(p) for p in net.realizePitch('f2')]
['F2', 'B-2', 'E-3', 'A-3']
>>> [str(p) for p in net.realizePitch('f2', 1, 'f2', 'f6')]
['F2', 'B-2', 'E-3', 'A-3', 'D-4', 'G-4', 'C-5', 'F-5', 'A5', 'D6']
>>> net.getRelativeNodeDegree('f2', 1, 'a-3') # could be 4 or 1
1
>>> net.getRelativeNodeDegree('f2', 1, 'd-4') # 2 is correct
2
>>> net.getRelativeNodeDegree('f2', 1, 'g-4') # 3 is correct
3
>>> net.getRelativeNodeDegree('f2', 1, 'c-5') # could be 4 or 1
1
>>> net.getRelativeNodeDegree('f2', 1, 'e--6') # could be 4 or 1
1
>>> [str(p) for p in net.realizePitch('f6', 1, 'f2', 'f6')]
['G#2', 'C#3', 'F#3', 'B3', 'E4', 'A4', 'D5', 'G5', 'C6', 'F6']
>>> net.getRelativeNodeDegree('f6', 1, 'd5')
1
>>> net.getRelativeNodeDegree('f6', 1, 'g5')
2
>>> net.getRelativeNodeDegree('f6', 1, 'a4')
3
>>> net.getRelativeNodeDegree('f6', 1, 'e4')
2
>>> net.getRelativeNodeDegree('f6', 1, 'b3')
1
IntervalNetwork.getRelativeNodeId(pitchReference, nodeName, pitchTarget, comparisonAttribute='ps', direction='ascending', alteredDegrees=None)

Given a reference pitch assigned to node id, determine the relative node id of pitchTarget, even if displaced over multiple octaves

The nodeName parameter may be a Node object, a node degree, a terminus string, or a None (indicating ‘terminusLow’).

Returns None if no match.

If getNeighbor is True, or direction, the nearest node will be returned.

If more than one node defines the same pitch, Node weights are used to select a single node.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> net.getRelativeNodeId('a', 1, 'a4')
'terminusLow'
>>> net.getRelativeNodeId('a', 1, 'b4')
0
>>> net.getRelativeNodeId('a', 1, 'c#4')
1
>>> net.getRelativeNodeId('a', 1, 'c4', comparisonAttribute = 'step')
1
>>> net.getRelativeNodeId('a', 1, 'c', comparisonAttribute = 'step')
1
>>> net.getRelativeNodeId('a', 1, 'b-4') is None
True
IntervalNetwork.getUnalteredPitch(pitchObj, nodeObj, direction='bi', alteredDegrees=None)

Given a node, get the unaltered pitch.

IntervalNetwork.match(pitchReference, nodeId, pitchTarget, comparisonAttribute='pitchClass', alteredDegrees=None)

Given one or more pitches in pitchTarget, return a tuple of a list of matched pitches, and a list of unmatched pitches.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork(edgeList)
>>> [str(p) for p in net.realizePitch('e-2')]
['E-2', 'F2', 'G2', 'A-2', 'B-2', 'C3', 'D3', 'E-3']
>>> net.match('e-2', 1, 'c3') # if e- is tonic, is 'c3' in the scale?
([<music21.pitch.Pitch C3>], [])
>>> net.match('e-2', 1, 'd3')
([<music21.pitch.Pitch D3>], [])
>>> net.match('e-2', 1, 'd#3')
([<music21.pitch.Pitch D#3>], [])
>>> net.match('e-2', 1, 'e3')
([], [<music21.pitch.Pitch E3>])
>>> pitchTarget = [pitch.Pitch('b-2'), pitch.Pitch('b2'), pitch.Pitch('c3')]
>>> net.match('e-2', 1, pitchTarget)
([<music21.pitch.Pitch B-2>, <music21.pitch.Pitch C3>], [<music21.pitch.Pitch B2>])
>>> pitchTarget = ['b-2', 'b2', 'c3', 'e-3', 'e#3', 'f2', 'e--2']
>>> (matched, unmatched) = net.match('e-2', 1, pitchTarget)
>>> [str(p) for p in matched]
['B-2', 'C3', 'E-3', 'E#3', 'F2', 'E--2']
>>> unmatched
[<music21.pitch.Pitch B2>]
IntervalNetwork.nextPitch(pitchReference, nodeName, pitchOrigin, direction='ascending', stepSize=1, alteredDegrees=None, getNeighbor=True)

Given a pitchReference, nodeName, and a pitch origin, return the next pitch.

The nodeName parameter may be a Node object, a node degree, a terminus string, or a None (indicating ‘terminusLow’).

The stepSize parameter can be configured to permit different sized steps in the specified direction.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.nextPitch('g', 1, 'f#5', 'ascending')
<music21.pitch.Pitch G5>
>>> net.nextPitch('g', 1, 'f#5', 'descending')
<music21.pitch.Pitch E5>
>>> net.nextPitch('g', 1, 'f#5', 'ascending', 2) # two steps
<music21.pitch.Pitch A5>
>>> alteredDegrees = {2:{'direction':'bi', 'interval':interval.Interval('-a1')}}
>>> net.nextPitch('g', 1, 'g2', 'ascending', alteredDegrees=alteredDegrees)
<music21.pitch.Pitch A-2>
>>> net.nextPitch('g', 1, 'a-2', 'ascending', alteredDegrees=alteredDegrees)
<music21.pitch.Pitch B2>
IntervalNetwork.nodeIdToDegree(nId, direction=None)

Given a strict node id (the .id attribute of the Node), return the degree.

There may not be unambiguous way to determine degree. Or, a degree may have different meanings when ascending or descending.

IntervalNetwork.nodeIdToEdgeDirections(nId)

Given a Node id, find all edges associated with this node and report on their directions

>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillMelodicMinor()
>>> net.nodeIdToEdgeDirections('terminusLow')
['bi']
>>> net.nodeIdToEdgeDirections(0)
['bi', 'bi']
>>> net.nodeIdToEdgeDirections(6)
['ascending', 'ascending']
>>> net.nodeIdToEdgeDirections(5)
['descending', 'descending']

This node has bi-directional (from below), ascending (to above), and descending (from above) edge connections connections

>>> net.nodeIdToEdgeDirections(3)
['bi', 'ascending', 'descending']
IntervalNetwork.nodeNameToNodes(nodeId, equateTermini=True, permitDegreeModuli=True)

The nodeName parameter may be a Node object, a node degree (as a number), a terminus string, or a None (indicating ‘terminusLow’).

Return a list of Node objects that match this identifications.

If equateTermini is True, and the name given is a degree number, then the first terminal will return both the first and last.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.nodeNameToNodes(1)[0]
<music21.scale.intervalNetwork.Node id='terminusLow'>
>>> net.nodeNameToNodes('high')
[<music21.scale.intervalNetwork.Node id='terminusHigh'>]
>>> net.nodeNameToNodes('low')
[<music21.scale.intervalNetwork.Node id='terminusLow'>]

Test using a nodeStep, or an integer nodeName

>>> net.nodeNameToNodes(1)
[<music21.scale.intervalNetwork.Node id='terminusLow'>,
 <music21.scale.intervalNetwork.Node id='terminusHigh'>]
>>> net.nodeNameToNodes(1, equateTermini=False)
[<music21.scale.intervalNetwork.Node id='terminusLow'>]
>>> net.nodeNameToNodes(2)
[<music21.scale.intervalNetwork.Node id=0>]

With degree moduli, degree zero is the top-most non-terminal (as terminals are redundant

>>> net.nodeNameToNodes(0)
[<music21.scale.intervalNetwork.Node id=5>]
>>> net.nodeNameToNodes(-1)
[<music21.scale.intervalNetwork.Node id=4>]
>>> net.nodeNameToNodes(8)
[<music21.scale.intervalNetwork.Node id='terminusLow'>,
 <music21.scale.intervalNetwork.Node id='terminusHigh'>]
IntervalNetwork.plot(pitchObj=None, nodeId=None, minPitch=None, maxPitch=None, *args, **keywords)

Given a method and keyword configuration arguments, create and display a plot.

Requires networkx to be installed.

IntervalNetwork.processAlteredNodes(alteredDegrees, n, p, direction)

Return an altered pitch for given node, if an alteration is specified in the alteredDegrees dictionary

IntervalNetwork.realize(pitchReference, nodeId=None, minPitch=None, maxPitch=None, direction='ascending', alteredDegrees=None, reverse=False)

Realize the nodes of this network based on a pitch assigned to a valid nodeId, where nodeId can be specified by integer (starting from 1) or key (a tuple of origin, destination keys).

Without a min or max pitch, the given pitch reference is assigned to the designated node, and then both ascends to the terminus and descends to the terminus.

The alteredDegrees dictionary permits creating mappings between node degree and direction and Interval based transpositions.

Returns two lists, a list of pitches, and a list of Node keys.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> (pitches, nodeKeys) = net.realize('c2', 1, 'c2', 'c3')
>>> [str(p) for p in pitches]
['C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B2', 'C3']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5, 'terminusHigh']
>>> alteredDegrees = {7:{'direction':'bi', 'interval':interval.Interval('-a1')}}
>>> (pitches, nodeKeys) = net.realize('c2', 1, 'c2', 'c4', alteredDegrees=alteredDegrees)
>>> [str(p) for p in pitches]
['C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B-2', 'C3',
 'D3', 'E3', 'F3', 'G3', 'A3', 'B-3', 'C4']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5, 'terminusHigh', 0, 1, 2, 3, 4, 5, 'terminusHigh']
IntervalNetwork.realizeAscending(pitchReference, nodeId=None, minPitch=None, maxPitch=None, alteredDegrees=None, fillMinMaxIfNone=False)

Given a reference pitch, realize upwards to a maximum pitch.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> (pitches, nodeKeys) = net.realizeAscending('c2', 1, 'c5', 'c6')
>>> [str(p) for p in pitches]
['C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5', 'C6']
>>> nodeKeys
['terminusHigh', 0, 1, 2, 3, 4, 5, 'terminusHigh']
>>> net = scale.intervalNetwork.IntervalNetwork(octaveDuplicating=True)
>>> net.fillBiDirectedEdges(edgeList)
>>> (pitches, nodeKeys) = net.realizeAscending('c2', 1, 'c5', 'c6')
>>> [str(p) for p in pitches]
['C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5', 'C6']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5, 'terminusHigh']
IntervalNetwork.realizeDescending(pitchReference, nodeId=None, minPitch=None, maxPitch=None, alteredDegrees=None, includeFirst=False, fillMinMaxIfNone=False, reverse=True)

Given a reference pitch, realize downward to a minimum.

If no minimum is is given, the terminus is used.

If includeFirst is False, the starting (highest) pitch will not be included.

If fillMinMaxIfNone is True, a min and max will be artificially derived from an ascending scale and used as min and max values.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.realizeDescending('c2', 1, 'c3') # minimum is above ref
([], [])
>>> (pitches, nodeKeys) = net.realizeDescending('c3', 1, 'c2')
>>> [str(p) for p in pitches]
['C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B2']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5]
>>> (pitches, nodeKeys) = net.realizeDescending('c3', 1, 'c2', includeFirst=True)
>>> [str(p) for p in pitches]
['C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B2', 'C3']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5, 'terminusLow']
>>> (pitches, nodeKeys) = net.realizeDescending('a6', 'high')
>>> [str(p) for p in pitches]
['A5', 'B5', 'C#6', 'D6', 'E6', 'F#6', 'G#6']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5]
>>> (pitches, nodeKeys) = net.realizeDescending('a6', 'high', includeFirst=True)
>>> [str(p) for p in pitches]
['A5', 'B5', 'C#6', 'D6', 'E6', 'F#6', 'G#6', 'A6']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5, 'terminusHigh']
>>> net = scale.intervalNetwork.IntervalNetwork(octaveDuplicating=True)
>>> net.fillBiDirectedEdges(edgeList)
>>> (pitches, nodeKeys) = net.realizeDescending('c2', 1, 'c0', 'c1')
>>> [str(p) for p in pitches]
['C0', 'D0', 'E0', 'F0', 'G0', 'A0', 'B0']
>>> nodeKeys
['terminusLow', 0, 1, 2, 3, 4, 5]
IntervalNetwork.realizeIntervals(nodeId=None, minPitch=None, maxPitch=None, direction='ascending', alteredDegrees=None, reverse=False)

Realize the sequence of intervals between the specified pitches, or the termini.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.realizeIntervals()
[<music21.interval.Interval M2>, <music21.interval.Interval M2>,
 <music21.interval.Interval m2>, <music21.interval.Interval M2>,
 <music21.interval.Interval M2>, <music21.interval.Interval M2>,
 <music21.interval.Interval m2>]
IntervalNetwork.realizeMinMax(pitchReference, nodeId=None, alteredDegrees=None)

Realize the min and max pitches of the scale, or the min and max values found between two termini.

This suggests that min and max might be beyond the terminus.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
IntervalNetwork.realizePitch(pitchReference, nodeId=None, minPitch=None, maxPitch=None, direction='ascending', alteredDegrees=None, reverse=False)

Realize the native nodes of this network based on a pitch assigned to a valid nodeId, where nodeId can be specified by integer (starting from 1) or key (a tuple of origin, destination keys).

The nodeId, when a simple, linear network, can be used as a scale degree value starting from one.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> [str(p) for p in net.realizePitch(pitch.Pitch('G3'))]
['G3', 'A3', 'B3', 'C4', 'D4', 'E4', 'F#4', 'G4']

G3 is the fifth (scale) degree

>>> [str(p) for p in net.realizePitch(pitch.Pitch('G3'), 5)]
['C3', 'D3', 'E3', 'F3', 'G3', 'A3', 'B3', 'C4']

G3 is the seventh (scale) degree

>>> [str(p) for p in net.realizePitch(pitch.Pitch('G3'), 7) ]
['A-2', 'B-2', 'C3', 'D-3', 'E-3', 'F3', 'G3', 'A-3']
>>> [str(p) for p in net.realizePitch(pitch.Pitch('f#3'), 1, 'f2', 'f3') ]
['E#2', 'F#2', 'G#2', 'A#2', 'B2', 'C#3', 'D#3', 'E#3']
>>> [str(p) for p in net.realizePitch(pitch.Pitch('a#2'), 7, 'c6', 'c7')]
['C#6', 'D#6', 'E6', 'F#6', 'G#6', 'A#6', 'B6']

Circle of fifths

>>> edgeList = ['P5'] * 6 + ['d6'] + ['P5'] * 5
>>> net5ths = scale.intervalNetwork.IntervalNetwork()
>>> net5ths.fillBiDirectedEdges(edgeList)
>>> [str(p) for p in net5ths.realizePitch(pitch.Pitch('C1'))]
['C1', 'G1', 'D2', 'A2', 'E3', 'B3', 'F#4', 'D-5', 'A-5', 'E-6', 'B-6', 'F7', 'C8']
>>> [str(p) for p in net5ths.realizePitch(pitch.Pitch('C2'))]
['C2', 'G2', 'D3', 'A3', 'E4', 'B4', 'F#5', 'D-6', 'A-6', 'E-7', 'B-7', 'F8', 'C9']
IntervalNetwork.realizePitchByDegree(pitchReference, nodeId=None, nodeDegreeTargets=(1, ), minPitch=None, maxPitch=None, direction='ascending', alteredDegrees=None)

Realize the native nodes of this network based on a pitch assigned to a valid nodeId, where nodeId can be specified by integer (starting from 1) or key (a tuple of origin, destination keys).

The nodeDegreeTargets specifies the degrees to be included within the specified range.

Example: build a network of the Major scale:

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)

Now for every “scale” where G is the 3rd degree, give me the tonic if that note is between C2 and C3. (There’s only one such note: E-2).

>>> net.realizePitchByDegree('G', 3, [1], 'c2', 'c3')
[<music21.pitch.Pitch E-2>]

But between c2 and f3 there are two, E-2 and E-3 (it doesn’t matter that the G which is scale degree 3 for E-3 is above F3):

>>> net.realizePitchByDegree('G', 3, [1], 'c2', 'f3')
[<music21.pitch.Pitch E-2>, <music21.pitch.Pitch E-3>]

Give us nodes 1, 2, and 5 for scales where G is node 5 (e.g., C major’s dominant) where any pitch is between C2 and F4

>>> pitchList = net.realizePitchByDegree('G', 5, [1, 2, 5], 'c2', 'f4')
>>> print(' '.join([str(p) for p in pitchList]))
C2 D2 G2 C3 D3 G3 C4 D4

There are no networks based on the major scale’s edge-list where with node 1 (i.e. “tonic”) between C2 and F2 where G is scale degree 7

>>> net.realizePitchByDegree('G', 7, [1], 'c2', 'f2')
[]
IntervalNetwork.realizeTermini(pitchReference, nodeId=None, alteredDegrees=None)

Realize the pitches of the ‘natural’ terminus of a network. This (presently) m ust be done by ascending, and assumes only one valid terminus for both extremes.

This suggests that in practice termini should not be affected by directionality.

>>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2']
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> net.fillBiDirectedEdges(edgeList)
>>> net.realizeTermini(pitch.Pitch('G3'))
(<music21.pitch.Pitch G3>, <music21.pitch.Pitch G4>)
>>> net.realizeTermini(pitch.Pitch('a6'))
(<music21.pitch.Pitch A6>, <music21.pitch.Pitch A7>)
IntervalNetwork.transposePitchAndApplySimplification(intervalObj, pitchObj)

transposes the pitch according to the given interval object and uses the simplification of the pitchSimplification property to simplify it afterwards.

>>> b = scale.intervalNetwork.IntervalNetwork()
>>> b.pitchSimplification # default
'maxAccidental'
>>> i = interval.Interval('m2')
>>> p = pitch.Pitch('C4')
>>> allPitches = []
>>> for j in range(15):
...    p = b.transposePitchAndApplySimplification(i, p)
...    allPitches.append(p.nameWithOctave)
>>> allPitches
['D-4', 'D4', 'E-4', 'F-4', 'F4', 'G-4', 'G4', 'A-4', 'A4',
 'B-4', 'C-5', 'C5', 'D-5', 'D5', 'E-5']
>>> b.pitchSimplification = 'mostCommon'
>>> p = pitch.Pitch('C4')
>>> allPitches = []
>>> for j in range(15):
...    p = b.transposePitchAndApplySimplification(i, p)
...    allPitches.append(p.nameWithOctave)
>>> allPitches
['C#4', 'D4', 'E-4', 'E4', 'F4', 'F#4', 'G4', 'A-4', 'A4', 'B-4',
 'B4', 'C5', 'C#5', 'D5', 'E-5']

PitchSimplification can also be specified in the creation of the IntervalNetwork object

>>> b = scale.intervalNetwork.IntervalNetwork(pitchSimplification=None)
>>> p = pitch.Pitch('C4')
>>> allPitches = []
>>> for j in range(5):
...    p = b.transposePitchAndApplySimplification(i, p)
...    allPitches.append(p.nameWithOctave)
>>> allPitches
['D-4', 'E--4', 'F--4', 'G---4', 'A----4']

Note that beyond quadruple flats or sharps, pitchSimplification is automatic:

>>> p
<music21.pitch.Pitch A----4>
>>> b.transposePitchAndApplySimplification(i, p)
<music21.pitch.Pitch F#4>
IntervalNetwork.weightedSelection(edges, nodes)

Perform weighted random selection on a parallel list of edges and corresponding nodes.

>>> n1 = scale.intervalNetwork.Node(id='a', weight=1000000)
>>> n2 = scale.intervalNetwork.Node(id='b', weight=1)
>>> e1 = scale.intervalNetwork.Edge(interval.Interval('m3'), id='a')
>>> e2 = scale.intervalNetwork.Edge(interval.Interval('m3'), id='b')
>>> net = scale.intervalNetwork.IntervalNetwork()
>>> e, n = net.weightedSelection([e1, e2], [n1, n2])
>>> e.id # note: this may fail as there is a slight chance to get 'b'
'a'
>>> n.id
'a'

Node

class music21.scale.intervalNetwork.Node(id=None, degree=None, weight=1.0)

Abstraction of an unrealized Pitch Node.

The Node id is used to store connections in Edges and has no real meaning.

Terminal Nodes have special ids: ‘terminusLow’, ‘terminusHigh’

The Node degree is translated to scale degrees in various applications, and is used to request a pitch from the network.

The weight attribute is used to probabilistically select between multiple nodes when multiple nodes satisfy either a branching option in a pathway or a request for a degree.

TODO: replace w/ NamedTuple; eliminate id, and have a terminus: low, high, None

Node bases

Edge

class music21.scale.intervalNetwork.Edge(intervalData=None, id=None, direction='bi')

Abstraction of an Interval as an Edge.

Edges store an Interval object as well as a pathway direction specification. The pathway is the route through the network from terminus to terminus, and can either by ascending or descending.

For directed Edges, the direction of the Interval may be used to suggest non-pitch ascending movements (even if the pathway direction is ascending).

Weight values, as well as other attributes, can be stored.

>>> i = interval.Interval('M3')
>>> e = scale.intervalNetwork.Edge(i)
>>> e.interval is i
True
>>> e.direction
'bi'

Return the stored Interval object

>>> i = interval.Interval('M3')
>>> e1 = scale.intervalNetwork.Edge(i, id=0)
>>> n1 = scale.intervalNetwork.Node(id=0)
>>> n2 = scale.intervalNetwork.Node(id=1)
>>> e1.addDirectedConnection(n1, n2, 'ascending')
>>> e1.interval
<music21.interval.Interval M3>

Return the direction of the Edge.

>>> i = interval.Interval('M3')
>>> e1 = scale.intervalNetwork.Edge(i, id=0)
>>> n1 = scale.intervalNetwork.Node(id=0)
>>> n2 = scale.intervalNetwork.Node(id=1)
>>> e1.addDirectedConnection(n1, n2, 'ascending')
>>> e1.direction
'ascending'

Edge read-only properties

Edge.connections

Callable as a property (.connections) or as a method (.getConnections(direction)):

Return a list of connections between Nodes, represented as pairs of Node ids. If a direction is specified, and if the Edge is directional, only the desired directed values will be returned.

>>> i = interval.Interval('M3')
>>> e1 = scale.intervalNetwork.Edge(i, id=0)
>>> n1 = scale.intervalNetwork.Node(id='terminusLow')
>>> n2 = scale.intervalNetwork.Node(id=1)
>>> e1.addBiDirectedConnections(n1, n2)
>>> e1.connections
[('terminusLow', 1), (1, 'terminusLow')]
>>> e1.getConnections('ascending')
[('terminusLow', 1)]
>>> e1.getConnections('descending')
[(1, 'terminusLow')]

Edge methods

Edge.addBiDirectedConnections(node1, node2)

Provide two Edge objects that pass through this Node, in the direction from the first to the second.

>>> i = interval.Interval('M3')
>>> e1 = scale.intervalNetwork.Edge(i, id=0)
>>> n1 = scale.intervalNetwork.Node(id='terminusLow')
>>> n2 = scale.intervalNetwork.Node(id=1)
>>> e1.addBiDirectedConnections(n1, n2)
>>> e1.connections
[('terminusLow', 1), (1, 'terminusLow')]
>>> e1
<music21.scale.intervalNetwork.Edge bi M3 [('terminusLow', 1), (1, 'terminusLow')]>
Edge.addDirectedConnection(node1, node2, direction=None)

Provide two Node objects that are connected by this Edge, in the direction from the first to the second.

When calling directly, a direction, either ascending or descending, should be set here; this will override whatever the interval is. If None, this will not be set.

>>> i = interval.Interval('M3')
>>> e1 = scale.intervalNetwork.Edge(i, id=0)
>>> n1 = scale.intervalNetwork.Node(id=0)
>>> n2 = scale.intervalNetwork.Node(id=1)
>>> e1.addDirectedConnection(n1, n2, 'ascending')
>>> e1.connections
[(0, 1)]
>>> e1
<music21.scale.intervalNetwork.Edge ascending M3 [(0, 1)]>
Edge.getConnections(direction=None)

Callable as a property (.connections) or as a method (.getConnections(direction)):

Return a list of connections between Nodes, represented as pairs of Node ids. If a direction is specified, and if the Edge is directional, only the desired directed values will be returned.

>>> i = interval.Interval('M3')
>>> e1 = scale.intervalNetwork.Edge(i, id=0)
>>> n1 = scale.intervalNetwork.Node(id='terminusLow')
>>> n2 = scale.intervalNetwork.Node(id=1)
>>> e1.addBiDirectedConnections(n1, n2)
>>> e1.connections
[('terminusLow', 1), (1, 'terminusLow')]
>>> e1.getConnections('ascending')
[('terminusLow', 1)]
>>> e1.getConnections('descending')
[(1, 'terminusLow')]