PiCoGen / sheetsage /theory /internal.py
Vansh Chugh
vendor jukebox+sheetsage source and recovered weights, use_jukebox=True
ff7b988
Raw
History Blame Contribute Delete
10.3 kB
import numpy as np
from .basic import PitchClass, PitchInterval
_LILY_NAME_TO_SCALE_DEGREES = {
"major": (2, 2, 1, 2, 2, 2),
"dorian": (2, 1, 2, 2, 2, 1),
"phrygian": (1, 2, 2, 2, 1, 2),
"lydian": (2, 2, 2, 1, 2, 2),
"mixolydian": (2, 2, 1, 2, 2, 1),
"minor": (2, 1, 2, 2, 1, 2),
"locrian": (1, 2, 2, 1, 2, 2),
}
_SCALE_DEGREES_TO_LILY_NAME = {v: k for k, v in _LILY_NAME_TO_SCALE_DEGREES.items()}
_KEY_TO_ENHARMONICS = {}
for o, ln in zip(
[0, 2, 4, 5, 7, 9, 11],
["major", "dorian", "phrygian", "lydian", "mixolydian", "minor", "locrian"],
):
for i, pc in enumerate(range(12)):
pc = (o + (pc * 7)) % 12
ks = (pc, _LILY_NAME_TO_SCALE_DEGREES[ln])
enharmonics = "#" if i <= 6 else "b"
_KEY_TO_ENHARMONICS[ks] = enharmonics
_CHORD_DEGREES_TO_LILY_NAME = {
(4, 3): "", # Major
(3, 4): "m", # Minor
(3, 4, 3): "m7", # Minor 7
(4, 3, 3): "7", # 7
(4, 3, 4): "maj7", # Major 7
(5, 2): "sus4", # Sus 4
(4, 3, 7): "9^7", # Add 9
(3, 3): "dim", # Dim
(3, 3, 4): "m7.5-", # Dim minor 7
(2, 5): "sus2", # Sus 2
(5, 2, 3): "7sus4", # 7 Sus 4
(3, 4, 3, 4): "m9", # Minor 9
(4, 3, 4, 3): "maj9", # Major 9
(2, 5, 3): "7sus2", # 7 Sus 2
(3, 4, 7): "m9^7", # Minor Add 9
(3, 3, 3): "dim7", # Dim 7
(2, 5, 4): "maj7sus2", # Major 7 Sus 2
(4, 3, 3, 4): "9", # 9
(2, 3, 2): "11.9^7", # Sus 2 Sus 4
(4, 4): "aug", # Aug
}
_LILY_ABSOLUTE_OCTAVE = 3
class _ImmutableIterable(tuple):
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
class Meter(_ImmutableIterable):
# Semantics: https://en.wikipedia.org/wiki/Metre_(music)#Metric_structure
# Meter is defined by "pulse groups": collections of repeating patterns of accented pulses, i.e., measures
# A "pulse group" has a "primary" and "secondary" subdivision
# "Primary subdivision" is synonymous with "pulse"
# Primary subdivision names: "duple" = 2, "triple" = 3, "quadruple" = 4, ...
# Secondary subdivision names: "simple" = 2, "compound" = 3
# Here we add a tertiary subdivision which refers to the quantization
# Time signatures are abstractions of meter which also take readability into account
# E.g., 3/4 with sixteenth note subdivisions is (3, 2, 2), because we have a simple triple meter w/ each secondary subdivision divided twice
def __new__(cls, primary, secondary, tertiary):
meter = (primary, secondary, tertiary)
if meter not in [(3, 2, 2), (4, 2, 2)]:
raise NotImplementedError()
return super().__new__(cls, meter)
def as_lily(self):
assert self in [(3, 2, 2), (4, 2, 2)]
if self == (3, 2, 2):
result = ("3", "4")
else:
result = ("4", "4")
return result
class Tempo(_ImmutableIterable):
def __new__(cls, pulses_per_minute):
if not isinstance(pulses_per_minute, int):
raise TypeError()
if pulses_per_minute <= 0:
raise ValueError()
return super().__new__(cls, (pulses_per_minute,))
def as_lily(self, meter):
if not isinstance(meter, Meter):
raise TypeError()
assert meter in [(3, 2, 2), (4, 2, 2)]
return ("4", str(self[0]))
class Key(_ImmutableIterable):
def __new__(cls, root_pc, scale_degree_pis):
if not isinstance(root_pc, int):
raise TypeError()
if not isinstance(scale_degree_pis, tuple):
raise TypeError()
for pi in scale_degree_pis:
if pi <= 0:
raise ValueError()
if sum(scale_degree_pis) >= 12:
raise ValueError()
return super().__new__(
cls,
(PitchClass(root_pc), tuple(PitchInterval(pi) for pi in scale_degree_pis)),
)
def as_lily(self):
enharmonics = _KEY_TO_ENHARMONICS.get(self)
if enharmonics is None:
raise ValueError("Unknown scale")
root = self[0].as_human_pitch_name(enharmonics=enharmonics).as_lily_pitch_name()
scale_name = _SCALE_DEGREES_TO_LILY_NAME.get(self[1])
assert scale_name is not None
return (root, scale_name)
class Note(_ImmutableIterable):
def __new__(cls, pc, octave):
if not isinstance(pc, int):
raise TypeError()
if not isinstance(octave, int):
raise TypeError()
return super().__new__(cls, (PitchClass(pc), int(octave)))
def as_lily(self, key, octave_0=4):
if not isinstance(key, Key):
raise TypeError()
enharmonics = _KEY_TO_ENHARMONICS.get(key)
if enharmonics is None:
raise ValueError()
pitch_class_str = (
self[0].as_human_pitch_name(enharmonics=enharmonics).as_lily_pitch_name()
)
octave = (octave_0 - _LILY_ABSOLUTE_OCTAVE) + self[1]
octave_str = ("'" if octave > 0 else ",") * abs(octave)
return (pitch_class_str, octave_str)
def as_midi_pitch(self, octave_0=4):
return 12 + (12 * (octave_0 + self[1])) + self[0]
class Chord(_ImmutableIterable):
def __new__(cls, root_pc, chord_degree_pis):
if root_pc is None:
if chord_degree_pis is not None:
raise ValueError()
t = (None, None)
else:
if not isinstance(root_pc, int):
raise TypeError()
if not isinstance(chord_degree_pis, tuple):
raise TypeError()
for pi in chord_degree_pis:
if pi <= 0:
raise ValueError()
t = (
PitchClass(root_pc),
tuple(PitchInterval(pi) for pi in chord_degree_pis),
)
return super().__new__(cls, t)
def as_lily(self, key):
if not isinstance(key, Key):
raise TypeError()
if self[0] is None:
assert self[1] is None
result = ("r", "")
else:
enharmonics = _KEY_TO_ENHARMONICS.get(key)
if enharmonics is None:
raise ValueError()
root_str = (
self[0]
.as_human_pitch_name(enharmonics=enharmonics)
.as_lily_pitch_name()
)
result = (root_str, _CHORD_DEGREES_TO_LILY_NAME.get(self[1], self[1]))
return result
def as_midi_pitches(self, octave_0=3):
if self[0] is None:
assert self[1] is None
result = []
else:
chord = [self[0]] + (self[0] + np.cumsum(self[1])).tolist()
result = (12 + (12 * octave_0) + np.array(chord)).tolist()
return result
class _InstantEvent(_ImmutableIterable):
def __new__(cls, event_cls, onset, event):
if not isinstance(onset, int):
raise TypeError()
if onset < 0:
raise ValueError()
event = event_cls(*event)
assert isinstance(onset, int)
assert isinstance(event, event_cls)
assert isinstance(event, _ImmutableIterable)
return super().__new__(cls, (onset, event))
class _SustainedEvent(_ImmutableIterable):
def __new__(cls, event_cls, onset, duration, event):
onset, event = _InstantEvent(event_cls, onset, event)
if not isinstance(duration, int):
raise TypeError()
if duration < 0:
raise ValueError()
assert isinstance(onset, int)
assert isinstance(duration, int)
assert isinstance(event, event_cls)
assert isinstance(event, _ImmutableIterable)
return super().__new__(cls, (onset, duration, event))
class _InstantEventList(_ImmutableIterable):
def __new__(cls, event_cls, *args):
# Instantiate and sort
instant_events = [_InstantEvent(event_cls, *ie) for ie in args]
instant_events = sorted(instant_events, key=lambda ie: ie[0])
# Check that no two start on same offset
offsets = set(ie[0] for ie in instant_events)
if len(offsets) != len(instant_events):
raise ValueError()
# Check that all events constitute a change
last_ie = None
for ie in instant_events:
if last_ie is not None and ie[1] == last_ie[1]:
raise ValueError()
last_ie = ie
return super().__new__(cls, tuple(instant_events))
class _SustainedEventList(_ImmutableIterable):
def __new__(cls, event_cls, *args):
# Instantiate and sort
sustained_events = [_SustainedEvent(event_cls, *se) for se in args]
sustained_events = sorted(sustained_events, key=lambda se: se[0])
# Check that no two start on same offset
offsets = set(se[0] for se in sustained_events)
if len(offsets) != len(sustained_events):
raise ValueError()
# Check that all durations are nonzero
if any(d == 0 for _, d, _ in sustained_events):
raise ValueError()
# Ensure monophonic
for i, se in enumerate(sustained_events):
if i + 1 < len(sustained_events):
max_duration = sustained_events[i + 1][0] - se[0]
assert max_duration > 0
if se[1] > max_duration:
raise ValueError()
return super().__new__(cls, tuple(sustained_events))
class _DefinedAtStartInstantEventList(_InstantEventList):
def __new__(cls, event_cls, *args):
instant_events = super().__new__(cls, event_cls, *args)
if len(instant_events) == 0 or instant_events[0][0] != 0:
raise ValueError()
return instant_events
class MeterChanges(_DefinedAtStartInstantEventList):
def __new__(cls, *args):
return super().__new__(cls, Meter, *args)
class TempoChanges(_DefinedAtStartInstantEventList):
def __new__(cls, *args):
return super().__new__(cls, Tempo, *args)
class KeyChanges(_DefinedAtStartInstantEventList):
def __new__(cls, *args):
return super().__new__(cls, Key, *args)
class Harmony(_InstantEventList):
def __new__(cls, *args):
return super().__new__(cls, Chord, *args)
class Melody(_SustainedEventList):
def __new__(cls, *args):
return super().__new__(cls, Note, *args)