Here is how to use Sage to list all of the ‘consonant’ chords of size n in a given scale. ‘Consonant’ here means basically whatever arbitrary standard you want, and a ‘consonant’ chord contains notes that are all consonant with at least one other note in the chord. In just intonation tuning systems, this can be particularly useful, as constructing harmony in music where performers may be tuning everything by ear such that every note is tune-able by ear makes a lot of sense. What I do:
Start with a pitch lattice representation of a scale.
Generate an undirected graph where vertices represent notes and edges represent consonant intervals.
Generate that graph’s adjacency matrix.
Plug the info into the following code, keeping in mind that Sage uses Python for coding, with all the formatting quirks like indentations:
Scale = Matrix({adjacency matrix})
ScaleGraph = Graph(Scale)
ScaleGraphList = list(ScaleGraph.connected_subgraph_iterator())
for X in range(len(ScaleGraphList)):
if ScaleGraphList[X].order()=={size of chord}:
print(ScaleGraphList[X].vertices())
Sage will list vertices as numbered from zero to the number of vertices minus one; as long as you keep track of how you constructed the adjacency matrix, these numbers translate directly to the scale notes you started with in the pitch lattice.
For example, given this lattice, where C=vertice 0; D=vertice 1; etc.:
I create this code:
Scale = Matrix([(0,0,1,1,1,1,0),(0,0,0,0,1,0,1),(1,0,0,0,1,1,1),(1,0,0,0,0,1,0),(1,1,1,0,0,0,1),(1,0,1,1,0,0,0),(0,1,1,0,1,0,0)])
ScaleGraph = Graph(Scale)
ScaleGraphList = list(ScaleGraph.connected_subgraph_iterator())
for X in range(len(ScaleGraphList)):
if ScaleGraphList[X].order()==3:
print(ScaleGraphList[X].vertices())
And I get back this list of triads:
[0, 2, 3]
[0, 2, 4]
[0, 2, 5]
[0, 2, 6]
[0, 3, 4]
[0, 3, 5]
[0, 1, 4]
[0, 4, 5]
[0, 4, 6]
[1, 2, 4]
[1, 4, 6]
[1, 2, 6]
[2, 4, 5]
[2, 4, 6]
[2, 3, 5]
[2, 5, 6]
Thus, that list of chords translates to:
CEF
CEG
CEA
CFG
CFA
CDG
CGA
CGB
DEF
DEB
DGB
EGA
EGB
EFA
EAB
This is kind of trivial for the major scale, but I wanted this tool to be able to ‘break down’ more complex scales and save myself many concussions. With the scale in adjacency matrix form, this also makes possible more strict analyses, such as only looking for complete subgraphs, or subgraphs with cycles, or any number of other graph theory tools.