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) pitch_classes: list[Pitch] = field(default=None)
notes: list[int] = 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) freqs: list[float] = field(default=None, init=False)
octaves: list[int] = field(default=None, init=False) octaves: list[int] = field(default=None, init=False)
durations: list[float] = field(default=None, init=False) durations: list[float] = field(default=None, init=False)
beats: 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]): def set_notes(self, notes: list[int]):
"""Set notes to the class""" """Set notes to the class"""
self.notes = notes 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): def update_notes(self, options):
"""Update notes""" """Update notes"""
notes = [] pitches, notes, freqs, octaves, durations, beats = ([] for _ in range(6))
freqs = []
octaves = []
durations = []
beats = []
# Update notes
for pitch in self.pitch_classes: for pitch in self.pitch_classes:
pitch.update_options(options) pitch.update_options(options)
pitch.update_note() 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) notes.append(pitch.note)
freqs.append(pitch.freq) freqs.append(pitch.freq)
octaves.append(pitch.octave) octaves.append(pitch.octave)
durations.append(pitch.duration) durations.append(pitch.duration)
beats.append(pitch.beat) beats.append(pitch.beat)
self.pitches = pitches
self.notes = notes self.notes = notes
self.freqs = freqs self.freqs = freqs
self.octaves = octaves self.octaves = octaves
self.duration = durations self.duration = durations
self.beats = beats 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 notes"""
return self.notes return self.notes
def get_octaves(self) -> int: def get_octaves(self) -> list:
"""Return octave""" """Return octave"""
return self.octaves return self.octaves
@ -314,6 +342,7 @@ class Chord(Event):
"""Return frequencies""" """Return frequencies"""
return self.freqs return self.freqs
@dataclass(kw_only=True) @dataclass(kw_only=True)
class RomanNumeral(Event): class RomanNumeral(Event):
"""Class for roman numbers""" """Class for roman numbers"""
@ -322,6 +351,7 @@ class RomanNumeral(Event):
chord_type: str = field(default=None) chord_type: str = field(default=None)
notes: list[int] = field(default_factory=[]) notes: list[int] = field(default_factory=[])
pitch_classes: list = None pitch_classes: list = None
inversions: int = None
def set_notes(self, chord_notes: list[int]): def set_notes(self, chord_notes: list[int]):
"""Set notes to roman numeral """Set notes to roman numeral
@ -485,17 +515,15 @@ class Sequence(Meta):
item = _create_chord_from_roman(item, options) item = _create_chord_from_roman(item, options)
return item return item
# pylint: disable=locally-disabled, unused-variable
def _generative_repeat(tree: list, times: int, options: dict): def _generative_repeat(tree: list, times: int, options: dict):
"""Repeats items and generates new random values""" """Repeats items and generates new random values"""
for i in range(times): for _ in range(times):
for item in tree.evaluate_tree(options): for item in tree.evaluate_tree(options):
yield from _resolve_item(item, options) yield from _resolve_item(item, options)
# pylint: disable=locally-disabled, unused-variable
def _normal_repeat(tree: list, times: int, options: dict): def _normal_repeat(tree: list, times: int, options: dict):
"""Repeats items with the same random values""" """Repeats items with the same random values"""
for i in range(times): for _ in range(times):
for item in tree: for item in tree:
yield from _resolve_item(item, options) yield from _resolve_item(item, options)
@ -597,7 +625,11 @@ class Sequence(Meta):
pitch_classes=pitch_classes, pitch_classes=pitch_classes,
notes=chord_notes, notes=chord_notes,
kwargs=options, kwargs=options,
inversions=current.inversions,
) )
chord.update_notes(options)
return chord return chord
# Start of the main function: Evaluate and flatten the Ziffers object tree # 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] 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: def sum_dict(arr: list[dict]) -> dict:
"""Sums a list of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4}""" """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)] bool_list = [_starts_descent(res_list, index) for index in range(length)]
return rotation(bool_list, rotate) return rotation(bool_list, rotate)

View File

@ -1648,6 +1648,9 @@ def __build_chords():
dim = [0, 3, 6] dim = [0, 3, 6]
dim7 = [0, 3, 6, 9] dim7 = [0, 3, 6, 9]
halfdim = [0, 3, 6, 10] halfdim = [0, 3, 6, 10]
aug7 = [0, 4, 8, 10]
aug9 = [0, 4, 10, 14]
six = [0, 4, 7, 9]
all_chords = { all_chords = {
"1": [0], "1": [0],
"5": [0, 7], "5": [0, 7],
@ -1655,12 +1658,12 @@ def __build_chords():
"m+5": [0, 3, 8], "m+5": [0, 3, 8],
"sus2": [0, 2, 7], "sus2": [0, 2, 7],
"sus4": [0, 5, 7], "sus4": [0, 5, 7],
"6": [0, 4, 7, 9], "6": six,
"m6": [0, 3, 7, 9], "m6": [0, 3, 7, 9],
"7sus2": [0, 2, 7, 10], "7sus2": [0, 2, 7, 10],
"7sus4": [0, 5, 7, 10], "7sus4": [0, 5, 7, 10],
"7-5": [0, 4, 6, 10], "7-5": [0, 4, 6, 10],
"7+5": [0, 4, 8, 10], "7+5": aug7,
"m7+5": [0, 3, 8, 10], "m7+5": [0, 3, 8, 10],
"9": [0, 4, 7, 10, 14], "9": [0, 4, 7, 10, 14],
"m9": [0, 3, 7, 10, 14], "m9": [0, 3, 7, 10, 14],
@ -1695,6 +1698,21 @@ def __build_chords():
"madd9": [0, 3, 7, 14], "madd9": [0, 3, 7, 14],
"madd11": [0, 3, 7, 17], "madd11": [0, 3, 7, 17],
"madd13": [0, 3, 7, 21], "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, "major": major,
"maj": major, "maj": major,
"M": major, "M": major,
@ -1705,7 +1723,6 @@ def __build_chords():
"dom7": dom7, "dom7": dom7,
"7": dom7, "7": dom7,
"M7": major7, "M7": major7,
"minor7": minor7,
"m7": minor7, "m7": minor7,
"augmented": aug, "augmented": aug,
"a": aug, "a": aug,

View File

@ -1,5 +1,5 @@
""" Lark transformer for mapping Lark tokens to Ziffers objects """ """ Lark transformer for mapping Lark tokens to Ziffers objects """
from lark import Transformer from lark import Transformer, Token
from .classes import ( from .classes import (
Ziffers, Ziffers,
Whitespace, Whitespace,
@ -161,23 +161,52 @@ class ZiffersTransformer(Transformer):
def chord(self, items): def chord(self, items):
"""Parses chord""" """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])) 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: def named_roman(self, items) -> RomanNumeral:
"""Parse chord from roman numeral""" """Parse chord from roman numeral"""
numeral = items[0].value numeral = items[0].value
if len(items) > 1: if len(items) == 1:
name = items[1]
chord_notes = chord_from_roman_numeral(numeral, name)
parsed_number = parse_roman(numeral)
return RomanNumeral( 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( if len(items) > 2:
value=parse_roman(numeral), name = items[1]
text=numeral, inversions = int(items[-1].value[1:])
notes=chord_from_roman_numeral(numeral), 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): def chord_name(self, item):
"""Return name for chord""" """Return name for chord"""

View File

@ -26,11 +26,13 @@
rest: prefix* "r" rest: prefix* "r"
// Chords // Chords
chord: pitch_class pitch_class+ chord: pitch_class pitch_class+ invert?
named_roman: roman_number (("^" chord_name))? // TODO: Add | ("+" number) named_roman: roman_number (("^" chord_name))? invert? // TODO: Add | ("+" number)
chord_name: /[a-zA-Z0-9]+/ chord_name: /[a-zA-Z0-9]+/
?roman_number: /iv|v|v?i{1,3}/ ?roman_number: /iv|v|v?i{1,3}/
invert: /%-?[0-9][0-9]*/
// Valid as integer // Valid as integer
number: NUMBER | random_integer | cycle number: NUMBER | random_integer | cycle