Created
January 22, 2025 03:36
-
-
Save jupdike/f6138924ca85e4d35dfd6bae9a97c73b to your computer and use it in GitHub Desktop.
Python code to generate tables of every set class (OPTC-equivalent) sorted by distance to the perfectly even C-note chord
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# run this with python3 | |
# See https://harmoniousapp.net/p/71/Set-Classes for more info | |
import math | |
# ------------------------ | |
# Utility functions for dealing with Set Classes | |
# a number in binary between 0 and 111111111111b (4093 decimal) can be converted | |
# to and from a list of integers, such as [0,4,7] which is 000010010001b or 145 decimal | |
def tolist(x): | |
ret = [] | |
for b in range(12): | |
if x & (1<<b): | |
ret.append(b) | |
return ret | |
def fromlist(l): | |
x = 0 | |
for e in l: | |
x += 1<<e | |
return x | |
def lowest(a): | |
aa = rotations(a) | |
aa.sort() | |
return aa[0] | |
# is a subbits of b? | |
def issubbits(a,b): | |
return a & b == a | |
def bitsset(x): | |
mask = 1 | |
ret = 0 | |
while mask <=x: | |
if mask & x: | |
ret += 1 | |
mask <<= 1 | |
return ret | |
def permute(l): | |
n = len(l) | |
return [l[i:]+l[:i] for i in range(n)] | |
def rightshift(x): | |
return ((x>>1) & 4095) | ((x & 1) << 11) | |
def leftshift(x): | |
if x & (1<<11): | |
return ( 1 | (x << 1) ) & 4095 | |
return (x << 1) & 4095 | |
def rotations(num): | |
ret = [] | |
r = num | |
for i in range(12): | |
ret.append(r) | |
r = rightshift(r) | |
return ret | |
clusters = [fromlist([ (x+0)%12, (x+1)%12, (x+2)%12 ]) for x in range(12)] | |
def hasCluster(c): | |
noClusters = True | |
for cluster in clusters: | |
if issubbits(cluster, c): | |
noClusters = False | |
break | |
return not noClusters | |
# t = ten | |
# e = eleven | |
ordinal = '0 1 2 3 4 5 6 7 8 9 t e'.split(' ') | |
def pic(setclass): | |
x = lowest(setclass) | |
ret = [] | |
for i in range(12): | |
if x & (1 << i): | |
ret.append(ordinal[i]) | |
return ''.join(ret) | |
# ------------------------ | |
# the main algorithm | |
def circle(card): | |
cardf = 1.0 * card | |
card = int(math.ceil(cardf)) | |
return [sorted([( (12.0*y/1000.0) + x*12.0/cardf)%12 for x in range(card)]) for y in range(1000)] | |
def bestcenter1(card, circ, x): | |
n = len(x) | |
bestD = 9999999 | |
for c in circ: | |
if (len(c) != n): | |
print("huh?") | |
exit(1) | |
continue | |
d2 = 0 | |
for i in range(n): | |
delta = c[i] - x[i] | |
delta = abs(delta) | |
if delta > 6: | |
delta = 12 - delta | |
d2 += delta*delta | |
if d2 < bestD: | |
bestD = d2 | |
bestD *= card | |
bestD = int(round(bestD)) | |
return bestD | |
def bestcenter(card, centers, num): | |
x = tolist(num) | |
bestD = 99999999 | |
for p in permute(x): | |
d = bestcenter1(card, centers, p) | |
if d < bestD: | |
bestD = d | |
return bestD | |
def makenumbers(card): | |
lowest_seen = set() | |
circ = circle(card) | |
results = [] | |
cardf = card*1.0 | |
card = int(math.ceil(cardf)) | |
for e in range(4096): | |
if e == 0: # don't crash for call to lowest(0) | |
continue | |
if not bitsset(e) == card: | |
continue | |
l = lowest(e) | |
if l in lowest_seen: | |
continue | |
lowest_seen.add(l) | |
best_d2_numerator = bestcenter(cardf, circ, e) | |
bestStr = "sqrt(%i/%i)" % (best_d2_numerator, card) | |
if best_d2_numerator == 0: | |
bestStr = "0" | |
# ccc = chromatic-cluster-containing | |
# cf = chromatic-cluster-free | |
cluster_str = "ccc" if hasCluster(e) else "cf" | |
results.append([best_d2_numerator, bestStr, e, pic(e), cluster_str]) | |
results.sort(key=lambda x: 10000 * x[0] + x[2]) | |
print(str(results).replace('],', '],\n')) | |
if __name__ == "__main__": | |
for c in [3, 4, 5, 6, 7, 8, 9]: | |
print() | |
print("Cardinality: %d" % c) | |
makenumbers(c) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment