Adding chord inversion
This commit is contained in:
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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(
|
|
||||||
text=numeral, value=parsed_number, chord_type=name, notes=chord_notes
|
|
||||||
)
|
|
||||||
return RomanNumeral(
|
return RomanNumeral(
|
||||||
value=parse_roman(numeral),
|
value=parse_roman(numeral),
|
||||||
text=numeral,
|
text=numeral,
|
||||||
notes=chord_from_roman_numeral(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):
|
def chord_name(self, item):
|
||||||
"""Return name for chord"""
|
"""Return name for chord"""
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user