Adding chord inversion

This commit is contained in:
2023-02-26 13:16:19 +02:00
parent ef27557c76
commit 443d4e6639
5 changed files with 120 additions and 27 deletions

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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"""

View File

@ -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