Skip to content

Instantly share code, notes, and snippets.

@jsawruk
Created September 22, 2012 15:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsawruk/3766546 to your computer and use it in GitHub Desktop.
Save jsawruk/3766546 to your computer and use it in GitHub Desktop.
Compute the pitch class normal form of a pitch class set
def rangeDiff(pcs):
# Return the difference between the first and last items of a list of pitch class sets
diff = (pcs[len(pcs) - 1] - pcs[0])
if diff < 0:
diff += 12
return diff
def cycleLeft(pcs):
# Return a single cyclic left permutation of a pitch class set
# Ex: [a,b,c] -> [b, c, a]
cycle = pcs[1:]
cycle.append(pcs[0])
return cycle
def cycleDiff(pcs):
# Given a pitch class set as a list
# Return a duple (2-tuple) of the form ([pcs], diff)
# Example:
# Input: [0,1,2,3]
# Output: ([0,1,2,3], 3)
return (pcs, rangeDiff(pcs))
def removeLast(list):
# Given a list, remove the last element
# Unlike pop(), which returns the value of the
# last element this function returns the
# modified list instead
return list[0:len(list) - 1]
def selectSmallest(pcsDupleList):
# From a list of PCS duples (2-tuples),
# select the N sets with the smallest
# outside intervals
# If N = 1, return
# Else, recurse until N = 1
# Use a lambda to sort by the difference
# the smallest difference will be first
pcsDupleList.sort(key = lambda x: x[1])
# Get the value of the smallest interval
smallestDiff = pcsDupleList[0][1]
# filter for the N with smallest outer intervals
pcsDupleList = filter(lambda x: x[1] == smallestDiff, pcsDupleList)
if len(pcsDupleList) == 1:
return pcsDupleList
else :
subsets = [cycleDiff(removeLast(x[0])) for x in pcsDupleList]
return selectSmallest(subsets)
def normalForm(pcs):
# Return the normal form of the input pitch class set
cycle = pcs
cycleCount = 0
cycles = []
while cycleCount < len(cycle) :
cycle = cycleLeft(cycle)
cycleCount += 1
# Insert into a quasi-associative array
# cannot use an actual associative array
# because lists are not hashable
# Format [([pcs], diff), ... ]
cycles.append(cycleDiff(cycle))
if cycle == pcs:
break
smallestSets = selectSmallest(cycles)
if len(smallestSets[0][0]) < len(cycles[0][0]) :
lengthDiff = len(cycles[0][0]) - len(smallestSets[0][0])
# create a list that only has subsets from the cycle list
# that start with the same number element
filterList = [x[0][:len(x[0]) - lengthDiff] for x in cycles]
# look for its position in the list
index = filterList.index(smallestSets[0][0])
# because the lists are sorted, the indexes match
smallestSets[0] = cycles[index]
return smallestSets[0][0]
def inversion(pcs):
# Determine the non-transposed inversion of the
# pitch class set by subtracting every element from 12
return [12 - x for x in pcs]
def zeroNormal(pcs):
# Convert the normal form so that the first element is 0
# By subtracting the first element from every element
# Add 12 if the result is less than 0
normal = normalForm(pcs)
zeroNormal = [x - normal[0] % 12 for x in normal]
# Note the unusual order for the ternary operator
return [x if x >= 0 else x + 12 for x in zeroNormal]
def primeForm(pcs):
# To determine the prime form, compute the zeroNormal of the set
# as well as the zeroNormal of its inversion
# Select whichever one is more packed to the left (smaller intervals first)
zeroNorm = zeroNormal(pcs)
zeroInv = sorted(zeroNormal(inversion(pcs)))
primeForm = zeroNorm
# Luckily, we can use Python's built-in list comparison
# to determine which set is more packed to the left
if zeroInv < zeroNorm :
primeForm = zeroInv
return primeForm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment