Spaces:
Sleeping
Sleeping
File size: 3,884 Bytes
ff7b988 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | import tempfile
from collections import Counter
from ..utils import run_cmd_sync
from .basic import HumanPitchName
from .internal import Harmony, Key, KeyChanges, Melody, MeterChanges
def theorytab_find_applicable(timed_events, search_event, eps=1e-3):
candidates = [t for t in timed_events if (search_event["beat"] - t["beat"]) > -eps]
if len(candidates) == 0:
raise ValueError()
return candidates[-1]
def estimate_key_changes(meter_changes, harmony, melody):
meter_changes = MeterChanges(*meter_changes)
harmony = Harmony(*harmony)
melody = Melody(*melody)
# Compute total num tertiary
meter = meter_changes[0][1]
assert meter in [(3, 2, 2), (4, 2, 2)]
tertiary_per_pulse = meter[1] * meter[2]
tertiary_per_group = meter[0] * tertiary_per_pulse
total_num_tertiary = 0 if len(harmony) == 0 else harmony[-1][0] + 1
total_num_tertiary = max(
total_num_tertiary, 0 if len(melody) == 0 else sum(melody[-1][:2])
)
while total_num_tertiary % tertiary_per_group != 0:
total_num_tertiary += 1
# Fake tempo
ppm = 120
tertiary_to_ms = lambda t: round((t / tertiary_per_pulse) / (ppm / 60) * 1000)
# "Beat" events
lines = []
for t in range(0, total_num_tertiary + tertiary_per_pulse, tertiary_per_pulse):
strength = 1
if t % tertiary_per_group == 0:
strength = 4
elif meter == (4, 2, 2) and t % (tertiary_per_pulse * 2) == 0:
strength = 2
lines.append(("Beat", tertiary_to_ms(t), strength))
# "Chord" events
pc_to_melisma_pc = {pc: (2 + (7 * pc)) % 12 for pc in range(12)}
for i, (t, c) in enumerate(harmony):
if i == 0:
t = 0
if i + 1 < len(harmony):
d = harmony[i + 1][0] - t
else:
d = total_num_tertiary - t
lines.append(
("Chord", tertiary_to_ms(t), tertiary_to_ms(t + d), pc_to_melisma_pc[c[0]])
)
# "Note" events
for t, d, n in melody:
lines.append(
("Note", tertiary_to_ms(t), tertiary_to_ms(t + d), n.as_midi_pitch())
)
parameters = """
verbosity=1
default_profile_value = 1.5
npc_or_tpc_profile=0
scoring_mode = 1
segment_beat_level=3
beat_printout_level=2
romnums=0
romnum_type=0
running=0
%CBMS MODEL
major_profile = 5.0 2.0 3.5 2.0 4.5 4.0 2.0 4.5 2.0 3.5 1.5 4.0
minor_profile = 5.0 2.0 3.5 4.5 2.0 4.0 2.0 4.5 3.5 2.0 1.5 4.0
change_penalty=12
%K-S MODEL
%major_profile = 6.35 2.23 3.48 2.33 4.38 4.09 2.52 5.19 2.39 3.66 2.29 2.88
%minor_profile = 6.33 2.68 3.52 5.38 2.60 3.53 2.54 4.75 3.98 2.69 3.34 3.17
%change_penalty = 2.3
%BAYESIAN MODEL
%major_profile = 0.748 0.060 0.488 0.082 0.670 0.460 0.096 0.715 0.104 0.366 0.057 0.400
%minor_profile = 0.712 0.084 0.474 0.618 0.049 0.460 0.105 0.747 0.404 0.067 0.133 0.330
%change_penalty = 0.002
""".strip()
formatted = "\n".join(["\t".join([str(a) for a in l]) for l in lines])
with tempfile.NamedTemporaryFile() as f, tempfile.NamedTemporaryFile() as p:
with open(f.name, "w") as f:
f.write(formatted)
with open(p.name, "w") as p:
p.write(parameters)
res, stdout, stderr = run_cmd_sync(
f"melisma-key -p {p.name} {f.name}", timeout=60
)
if res != 0 or len(stderr) > 0:
raise Exception(f"{stdout}\n{stderr}".strip())
key_to_count = Counter()
for key in stdout.split():
if key.endswith("m"):
scale = (2, 1, 2, 2, 1, 2)
key = key[:-1]
else:
scale = (2, 2, 1, 2, 2, 2)
key = Key(HumanPitchName(key).as_pitch_class(), scale)
key_to_count[key] += 1
if len(key_to_count) == 0:
raise Exception("Failed to estimate key")
key = sorted(key_to_count.keys(), key=lambda k: key_to_count[k])[-1]
return KeyChanges((0, key))
|