Adding chord inversion
This commit is contained in:
@ -262,43 +262,71 @@ class Chord(Event):
|
||||
|
||||
pitch_classes: list[Pitch] = field(default=None)
|
||||
notes: list[int] = field(default=None)
|
||||
inversions: int = field(default=None)
|
||||
pitches: list[int] = field(default=None, init=False)
|
||||
freqs: list[float] = field(default=None, init=False)
|
||||
octaves: list[int] = field(default=None, init=False)
|
||||
durations: list[float] = field(default=None, init=False)
|
||||
beats: list[float] = field(default=None, init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
if self.inversions is not None:
|
||||
self.invert(self.inversions)
|
||||
|
||||
def set_notes(self, notes: list[int]):
|
||||
"""Set notes to the class"""
|
||||
self.notes = notes
|
||||
|
||||
def invert(self, value: int):
|
||||
"""Chord inversion"""
|
||||
new_pitches = (
|
||||
list(reversed(self.pitch_classes)) if value < 0 else self.pitch_classes
|
||||
)
|
||||
for _ in range(abs(value)):
|
||||
new_pitch = new_pitches[_ % len(new_pitches)]
|
||||
if not new_pitch.local_options.get("octave"):
|
||||
new_pitch.local_options["octave"] = 0
|
||||
new_pitch.local_options["octave"] += -1 if value <= 0 else 1
|
||||
|
||||
self.pitch_classes = new_pitches
|
||||
|
||||
def update_notes(self, options):
|
||||
"""Update notes"""
|
||||
notes = []
|
||||
freqs = []
|
||||
octaves = []
|
||||
durations = []
|
||||
beats = []
|
||||
pitches, notes, freqs, octaves, durations, beats = ([] for _ in range(6))
|
||||
|
||||
# Update notes
|
||||
for pitch in self.pitch_classes:
|
||||
pitch.update_options(options)
|
||||
pitch.update_note()
|
||||
|
||||
# Sort by generated notes
|
||||
self.pitch_classes = sorted(self.pitch_classes, key=lambda x: x.note)
|
||||
|
||||
# Create helper lists
|
||||
for pitch in self.pitch_classes:
|
||||
pitches.append(pitch.pitch_class)
|
||||
notes.append(pitch.note)
|
||||
freqs.append(pitch.freq)
|
||||
octaves.append(pitch.octave)
|
||||
durations.append(pitch.duration)
|
||||
beats.append(pitch.beat)
|
||||
|
||||
self.pitches = pitches
|
||||
self.notes = notes
|
||||
self.freqs = freqs
|
||||
self.octaves = octaves
|
||||
self.duration = durations
|
||||
self.beats = beats
|
||||
|
||||
def get_notes(self) -> int:
|
||||
def get_pitches(self) -> list:
|
||||
"""Return pitch classes"""
|
||||
return self.pitches
|
||||
|
||||
def get_notes(self) -> list:
|
||||
"""Return notes"""
|
||||
return self.notes
|
||||
|
||||
def get_octaves(self) -> int:
|
||||
def get_octaves(self) -> list:
|
||||
"""Return octave"""
|
||||
return self.octaves
|
||||
|
||||
@ -314,6 +342,7 @@ class Chord(Event):
|
||||
"""Return frequencies"""
|
||||
return self.freqs
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class RomanNumeral(Event):
|
||||
"""Class for roman numbers"""
|
||||
@ -322,6 +351,7 @@ class RomanNumeral(Event):
|
||||
chord_type: str = field(default=None)
|
||||
notes: list[int] = field(default_factory=[])
|
||||
pitch_classes: list = None
|
||||
inversions: int = None
|
||||
|
||||
def set_notes(self, chord_notes: list[int]):
|
||||
"""Set notes to roman numeral
|
||||
@ -485,17 +515,15 @@ class Sequence(Meta):
|
||||
item = _create_chord_from_roman(item, options)
|
||||
return item
|
||||
|
||||
# pylint: disable=locally-disabled, unused-variable
|
||||
def _generative_repeat(tree: list, times: int, options: dict):
|
||||
"""Repeats items and generates new random values"""
|
||||
for i in range(times):
|
||||
for _ in range(times):
|
||||
for item in tree.evaluate_tree(options):
|
||||
yield from _resolve_item(item, options)
|
||||
|
||||
# pylint: disable=locally-disabled, unused-variable
|
||||
def _normal_repeat(tree: list, times: int, options: dict):
|
||||
"""Repeats items with the same random values"""
|
||||
for i in range(times):
|
||||
for _ in range(times):
|
||||
for item in tree:
|
||||
yield from _resolve_item(item, options)
|
||||
|
||||
@ -597,7 +625,11 @@ class Sequence(Meta):
|
||||
pitch_classes=pitch_classes,
|
||||
notes=chord_notes,
|
||||
kwargs=options,
|
||||
inversions=current.inversions,
|
||||
)
|
||||
|
||||
chord.update_notes(options)
|
||||
|
||||
return chord
|
||||
|
||||
# Start of the main function: Evaluate and flatten the Ziffers object tree
|
||||
|
||||
@ -10,6 +10,18 @@ def flatten(arr: list) -> list:
|
||||
else [arr]
|
||||
)
|
||||
|
||||
def rotate(arr, k):
|
||||
"""Rotates array"""
|
||||
# Calculate the effective rotation amount (mod the array length)
|
||||
k = k % len(arr)
|
||||
# Rotate the array to the right
|
||||
if k > 0:
|
||||
arr = arr[-k:] + arr[:-k]
|
||||
# Rotate the array to the left
|
||||
elif k < 0:
|
||||
arr = arr[-k:] + arr[:-k]
|
||||
return arr
|
||||
|
||||
|
||||
def sum_dict(arr: list[dict]) -> dict:
|
||||
"""Sums a list of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4}"""
|
||||
@ -73,3 +85,4 @@ def euclidian_rhythm(pulses: int, length: int, rotate: int = 0):
|
||||
bool_list = [_starts_descent(res_list, index) for index in range(length)]
|
||||
|
||||
return rotation(bool_list, rotate)
|
||||
|
||||
|
||||
@ -1648,6 +1648,9 @@ def __build_chords():
|
||||
dim = [0, 3, 6]
|
||||
dim7 = [0, 3, 6, 9]
|
||||
halfdim = [0, 3, 6, 10]
|
||||
aug7 = [0, 4, 8, 10]
|
||||
aug9 = [0, 4, 10, 14]
|
||||
six = [0, 4, 7, 9]
|
||||
all_chords = {
|
||||
"1": [0],
|
||||
"5": [0, 7],
|
||||
@ -1655,12 +1658,12 @@ def __build_chords():
|
||||
"m+5": [0, 3, 8],
|
||||
"sus2": [0, 2, 7],
|
||||
"sus4": [0, 5, 7],
|
||||
"6": [0, 4, 7, 9],
|
||||
"6": six,
|
||||
"m6": [0, 3, 7, 9],
|
||||
"7sus2": [0, 2, 7, 10],
|
||||
"7sus4": [0, 5, 7, 10],
|
||||
"7-5": [0, 4, 6, 10],
|
||||
"7+5": [0, 4, 8, 10],
|
||||
"7+5": aug7,
|
||||
"m7+5": [0, 3, 8, 10],
|
||||
"9": [0, 4, 7, 10, 14],
|
||||
"m9": [0, 3, 7, 10, 14],
|
||||
@ -1695,6 +1698,21 @@ def __build_chords():
|
||||
"madd9": [0, 3, 7, 14],
|
||||
"madd11": [0, 3, 7, 17],
|
||||
"madd13": [0, 3, 7, 21],
|
||||
"dim9": [0, 3, 6, 9, 14],
|
||||
"hdim7": halfdim,
|
||||
"hdim9": [0, 3, 6, 10, 14],
|
||||
"hdimb9": [0, 3, 6, 10, 13],
|
||||
"augMaj7": [0, 4, 8, 11],
|
||||
"minmaj7": [0, 3, 7, 11],
|
||||
"five": [0, 7, 12],
|
||||
"seven": dom7,
|
||||
"nine": aug9,
|
||||
"b9": [0, 4, 10, 13],
|
||||
"mM9": [0, 3, 11, 14],
|
||||
"min7": minor7,
|
||||
"min9": [0, 3, 10, 14],
|
||||
"b5": [0, 4, 6, 12],
|
||||
"mb5": [0, 3, 6, 12],
|
||||
"major": major,
|
||||
"maj": major,
|
||||
"M": major,
|
||||
@ -1705,7 +1723,6 @@ def __build_chords():
|
||||
"dom7": dom7,
|
||||
"7": dom7,
|
||||
"M7": major7,
|
||||
"minor7": minor7,
|
||||
"m7": minor7,
|
||||
"augmented": aug,
|
||||
"a": aug,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
""" Lark transformer for mapping Lark tokens to Ziffers objects """
|
||||
from lark import Transformer
|
||||
from lark import Transformer, Token
|
||||
from .classes import (
|
||||
Ziffers,
|
||||
Whitespace,
|
||||
@ -161,23 +161,52 @@ class ZiffersTransformer(Transformer):
|
||||
|
||||
def chord(self, items):
|
||||
"""Parses chord"""
|
||||
if isinstance(items[-1], Token):
|
||||
return Chord(
|
||||
pitch_classes=items[0:-1],
|
||||
text="".join([val.text for val in items[0:-1]]),
|
||||
inversions=int(items[-1].value[1:]),
|
||||
)
|
||||
return Chord(pitch_classes=items, text="".join([val.text for val in items]))
|
||||
|
||||
def invert(self, items):
|
||||
return items[0]
|
||||
|
||||
def named_roman(self, items) -> RomanNumeral:
|
||||
"""Parse chord from roman numeral"""
|
||||
numeral = items[0].value
|
||||
if len(items) > 1:
|
||||
name = items[1]
|
||||
chord_notes = chord_from_roman_numeral(numeral, name)
|
||||
parsed_number = parse_roman(numeral)
|
||||
if len(items) == 1:
|
||||
return RomanNumeral(
|
||||
text=numeral, value=parsed_number, chord_type=name, notes=chord_notes
|
||||
value=parse_roman(numeral),
|
||||
text=numeral,
|
||||
notes=chord_from_roman_numeral(numeral),
|
||||
)
|
||||
return RomanNumeral(
|
||||
value=parse_roman(numeral),
|
||||
text=numeral,
|
||||
notes=chord_from_roman_numeral(numeral),
|
||||
)
|
||||
if len(items) > 2:
|
||||
name = items[1]
|
||||
inversions = int(items[-1].value[1:])
|
||||
return RomanNumeral(
|
||||
text=numeral,
|
||||
value=parse_roman(numeral),
|
||||
chord_type=name,
|
||||
notes=chord_from_roman_numeral(numeral, name),
|
||||
inversions=inversions,
|
||||
)
|
||||
elif len(items) == 2:
|
||||
if isinstance(items[-1], Token):
|
||||
inversions = int(items[-1].value[1:])
|
||||
return RomanNumeral(
|
||||
value=parse_roman(numeral),
|
||||
text=numeral,
|
||||
notes=chord_from_roman_numeral(numeral),
|
||||
inversions=inversions,
|
||||
)
|
||||
else:
|
||||
return RomanNumeral(
|
||||
value=parse_roman(numeral),
|
||||
text=numeral,
|
||||
chord_type=items[1],
|
||||
notes=chord_from_roman_numeral(numeral),
|
||||
)
|
||||
|
||||
def chord_name(self, item):
|
||||
"""Return name for chord"""
|
||||
|
||||
@ -26,10 +26,12 @@
|
||||
rest: prefix* "r"
|
||||
|
||||
// Chords
|
||||
chord: pitch_class pitch_class+
|
||||
named_roman: roman_number (("^" chord_name))? // TODO: Add | ("+" number)
|
||||
chord: pitch_class pitch_class+ invert?
|
||||
named_roman: roman_number (("^" chord_name))? invert? // TODO: Add | ("+" number)
|
||||
chord_name: /[a-zA-Z0-9]+/
|
||||
?roman_number: /iv|v|v?i{1,3}/
|
||||
|
||||
invert: /%-?[0-9][0-9]*/
|
||||
|
||||
// Valid as integer
|
||||
number: NUMBER | random_integer | cycle
|
||||
|
||||
Reference in New Issue
Block a user