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)