Spaces:
Sleeping
Sleeping
File size: 2,998 Bytes
187bf9a | 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 119 120 121 122 123 124 125 126 | """Common symbols and musical constants for generation engines."""
from __future__ import annotations
from dataclasses import dataclass
PITCH_CLASS = {
"C": 0,
"B#": 0,
"C#": 1,
"Db": 1,
"D": 2,
"D#": 3,
"Eb": 3,
"E": 4,
"Fb": 4,
"E#": 5,
"F": 5,
"F#": 6,
"Gb": 6,
"G": 7,
"G#": 8,
"Ab": 8,
"A": 9,
"A#": 10,
"Bb": 10,
"B": 11,
"Cb": 11,
}
PC_TO_SHARP_NAME = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
COMMON_DURATION_BEATS = {
"64th": 0.0625,
"32nd": 0.125,
"16th triplet": 1 / 6,
"16th": 0.25,
"eighth triplet": 1 / 3,
"dotted 16th": 0.375,
"eighth": 0.5,
"quarter triplet": 2 / 3,
"dotted eighth": 0.75,
"quarter": 1.0,
"dotted quarter": 1.5,
"half": 2.0,
"dotted half": 3.0,
"whole": 4.0,
}
ABC_DURATION = {
"64th": "/4",
"32nd": "/2",
"16th triplet": "/3",
"16th": "",
"eighth triplet": "4/3",
"dotted 16th": "3/2",
"eighth": "2",
"quarter triplet": "8/3",
"dotted eighth": "3",
"quarter": "4",
"dotted quarter": "6",
"half": "8",
"dotted half": "12",
"whole": "16",
}
DEFAULT_DURATIONS = {
"16th",
"eighth",
"quarter",
"half",
"dotted eighth",
"dotted quarter",
"eighth triplet",
"16th triplet",
}
START_SYMBOL = "__THEME_GENERATION_START__"
@dataclass(frozen=True)
class Symbol:
"""A key-relative pitch class plus notated duration."""
rpc: int
duration: str
def __str__(self) -> str:
return f"{self.rpc}:{self.duration}"
def duration_options(min_duration: str, duration_grid: str, allow_triplets: bool) -> set[str]:
if min_duration not in COMMON_DURATION_BEATS:
labels = ", ".join(sorted(COMMON_DURATION_BEATS))
raise ValueError(f"Unknown duration {min_duration!r}; choose one of: {labels}")
if duration_grid not in COMMON_DURATION_BEATS:
labels = ", ".join(sorted(COMMON_DURATION_BEATS))
raise ValueError(f"Unknown duration grid {duration_grid!r}; choose one of: {labels}")
min_beats = COMMON_DURATION_BEATS[min_duration]
grid_beats = COMMON_DURATION_BEATS[duration_grid]
allowed = set()
for duration in DEFAULT_DURATIONS:
duration_beats = COMMON_DURATION_BEATS[duration]
on_grid = abs((duration_beats / grid_beats) - round(duration_beats / grid_beats)) < 1e-9
regular_triplet = allow_triplets and "triplet" in duration
if duration_beats >= min_beats and (on_grid or regular_triplet):
allowed.add(duration)
return allowed
def duration_units(duration: str) -> int:
return round(COMMON_DURATION_BEATS[duration] * 12)
def is_triplet_duration(duration: str) -> bool:
return "triplet" in duration
def parse_key_root(raw_key: str | None) -> int | None:
if not raw_key:
return None
root = raw_key.split("%", 1)[0].strip().replace("m", "")
return PITCH_CLASS.get(root)
|