#!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()