instrument-generator/generate_instrument_2.py

190 lines
5.3 KiB
Python

#!python3
"""this one should be a little more ordered and also work with suffixes."""
import random
def dots(something):
t = something[:]
for i in range(3, int(len(something)*4/3)-1, 4):
t = t[:i] + "." + t[i:]
return t
class HSNode:
def __init__(self, parent, code, description):
self.parent = parent
self.code = code
self.description = description
self.nexts = dict()
self.suffixes = None
def printout(self):
code_with_dots = dots(self.code)
print(f"{code_with_dots}: {self.description}")
if self.suffixes is not None:
self.suffixes.printout()
for n in sorted(self.nexts.keys()):
self.nexts[n].printout()
def add_code(self, code, description):
if len(code) == 0:
raise Exception(f"some kinda problem trying to add a blank code to a listing for {self.code}")
digit = code[0]
if len(code) == 1:
if digit not in self.nexts:
self.nexts[digit] = HSNode(self, self.code + digit, description)
else:
self.printout()
raise Exception(f"Collision found! {self.code}, adding {code}")
return self.nexts[digit]
else:
if digit not in self.nexts:
self.nexts[digit] = HSNode(self, self.code + digit, "HUH???")
return self.nexts[digit].add_code(code[1:], description)
def add_suffix(self, code, description):
if self.suffixes is None:
self.suffixes = HSNode(None, "-", f"suffixes for {self.code}")
return self.suffixes.add_code(code, description)
def random_child(self, stopping_chances):
this_threshhold, rest_chances = stopping_chances[0], stopping_chances[1:]
if random.random() < this_threshhold:
return self
if not self.nexts:
return self
digit, next_node = random.choice(list(self.nexts.items()))
return next_node.random_child(rest_chances)
# actually build the data from the data file
taxonomy = HSNode(None, "", "an instrument")
with open("Hornbostel-Sachs") as f:
current = taxonomy
for line in f.readlines():
code = line.split(" ")[0].replace(".", "")
description = " ".join(line.split(" ")[1:]).strip()
if code.startswith("#"):
pass
elif code.startswith("-"):
current.add_suffix(code.replace("-", ""), description)
else:
current = taxonomy.add_code(code, description)
def generate_instrument():
# this defines a threshhold for quitting early dependent on the length of the code so far.
# so there's a 0% chance of stopping with a zero-length code, 0% with a 1-length code, 1% with 2-length...
chance_of_stopping = [0, 0, 0.01, 0.01, 0.01, 0.05, 0.05, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]
base_code = taxonomy.random_child(chance_of_stopping)
candidate_suffix_trees = set()
current = base_code
while current is not None:
if current.suffixes:
candidate_suffix_trees.add(current.suffixes)
current = current.parent
additionals = []
# a 15% chance of adding a second code??
while random.random() < 0.15:
length = len(base_code.code) # possible points to bifurcate at
bifurcation_point = max(random.randrange(length), random.randrange(length)) # weighted towards the end
steps_back = length - bifurcation_point
new_base = base_code
for x in range(steps_back):
new_base = base_code.parent
new_code = new_base.random_child([0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1])
additionals.append(new_code)
current = new_code
while current is not None:
if current.suffixes:
candidate_suffix_trees.add(current.suffixes)
current = current.parent
suffixes = []
chance_of_suffix = 0.5
for s in candidate_suffix_trees:
if random.random() > 0.5:
continue
suffixes.append(s.random_child([0, 0.25, 0.5, 0.5, 0.5, 0.5, 0.5]))
return (base_code, additionals, suffixes)
def explain(hs):
"""returns a list of lines explaining the meaning of each digit in the code passed in"""
# output the base code
ancestry = []
c = hs
while c is not None:
ancestry.append(c)
c = c.parent
output = []
for c in reversed(ancestry):
output.append(f"{dots(c.code)}: {c.description}")
return output
def print_random_instrument():
multi = False
base_code, additionals, suffixes = generate_instrument()
# print out the whole code
base_string = dots(base_code.code)
adds_string = "+".join(map(lambda x: dots(x.code), additionals))
if adds_string:
adds_string = "+" + adds_string
suffixes_string = "".join(map(lambda x: x.code, suffixes))
print(f"Generated instrument: {base_string}{adds_string}{suffixes_string}")
print()
output = []
# output the base code
output.extend(explain(base_code))
# output additional codes:
for code in additionals:
output.append("but also:")
for line in explain(code):
if line not in output:
output.append(line)
# output suffixes:
for code in suffixes:
output.extend(explain(code))
for x in output:
print(x)
print()
shapes = "round,rectangular,blobby,like an animal,triangular,long,short,oblong,squat,curved,straight,arched,bell-like,tubular".split(',')
shape = random.choice(shapes)
print(f"Its shape is {shape}.")
if random.random() < 0.6:
style = random.choice("sharp,soft,stationary,hand-held,bent,simple,decorated,sitting,standing".split(','))
print(f"It is a {style} instrument.")
material = random.choice("bone,wood,metal,ceramic,leather,gourds,glass,fiber".split(','))
print(f"It is made of {material}.")
if __name__ == "__main__":
#taxonomy.printout() # print out the whole taxonomy
#print("\n\n\n")
print_random_instrument()