Some refactoring

This commit is contained in:
2023-02-15 19:44:40 +02:00
parent 726dc42902
commit 10f66d0027
3 changed files with 1631 additions and 1583 deletions

View File

@ -4,7 +4,7 @@ from itertools import product, islice, cycle
import operator import operator
import random import random
from .defaults import DEFAULT_OPTIONS from .defaults import DEFAULT_OPTIONS
from .scale import note_from_pc, midi_to_pitch_class, midi_to_freq from .scale import note_from_pc, midi_to_pitch_class, midi_to_freq, get_scale_length
@dataclass(kw_only=True) @dataclass(kw_only=True)
@ -15,16 +15,16 @@ class Meta:
def __post_init__(self): def __post_init__(self):
if self.kwargs: if self.kwargs:
self.update_new(self.kwargs) self.update_options(self.kwargs)
def update(self, new_values): def replace_options(self, new_values):
"""Update attributes from dict""" """Replaces attribute values from dict"""
for key, value in new_values.items(): for key, value in new_values.items():
if hasattr(self, key): if hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
def update_new(self, new_values): def update_options(self, new_values):
"""Updates new attributes from dict""" """Updates attribute values only if value is None"""
for key, value in new_values.items(): for key, value in new_values.items():
if hasattr(self, key): if hasattr(self, key):
if getattr(self, key) is None: if getattr(self, key) is None:
@ -169,15 +169,13 @@ class RandomPitch(Event):
pitch_class: int = field(default=None) pitch_class: int = field(default=None)
# FIXME: Get scale length as max somehow? def get_value(self, options: dict) -> int:
# pylint: disable=locally-disabled, unused-argument
def get_value(self) -> int:
"""Return random value """Return random value
Returns: Returns:
int: Returns random pitch int: Returns random pitch
""" """
return random.randint(0, 9) return random.randint(0, get_scale_length(options.get("scale","Major")) if options else 9)
@dataclass(kw_only=True) @dataclass(kw_only=True)
@ -198,6 +196,14 @@ class Chord(Event):
"""Set notes to the class""" """Set notes to the class"""
self.notes = notes self.notes = notes
def update_notes(self, options):
"""Update notes"""
notes = []
for pitch in self.pitch_classes:
pitch.update_options(options)
pitch.update_note()
notes.append(pitch.note)
self.notes = notes
@dataclass(kw_only=True) @dataclass(kw_only=True)
class RomanNumeral(Event): class RomanNumeral(Event):
@ -277,41 +283,41 @@ class Sequence(Meta):
eval_tree (bool, optional): Flag for using the evaluated subtree. Defaults to False. eval_tree (bool, optional): Flag for using the evaluated subtree. Defaults to False.
""" """
def resolve_item(item: Meta, options: dict): def _resolve_item(item: Meta, options: dict):
"""Resolve cyclic value""" """Resolve cyclic value"""
if isinstance(item, Sequence): if isinstance(item, Sequence):
if isinstance(item, ListOperation): if isinstance(item, ListOperation):
yield from item.evaluate_tree(options, True) yield from item.evaluate_tree(options, True)
elif isinstance(item, RepeatedSequence): elif isinstance(item, RepeatedSequence):
repeats = item.repeats.get_value() repeats = item.repeats.get_value()
yield from normal_repeat(item.evaluated_values, repeats, options) yield from _normal_repeat(item.evaluated_values, repeats, options)
elif isinstance(item, RepeatedListSequence): elif isinstance(item, RepeatedListSequence):
repeats = item.repeats.get_value() repeats = item.repeats.get_value()
yield from generative_repeat(item, repeats, options) yield from _generative_repeat(item, repeats, options)
else: else:
yield from item.evaluate_tree(options) yield from item.evaluate_tree(options)
elif isinstance(item, Cyclic): elif isinstance(item, Cyclic):
yield from resolve_item(item.get_value(), options) yield from _resolve_item(item.get_value(), options)
elif isinstance(item, Modification): elif isinstance(item, Modification):
options = update_options(item, options) options = _update_options(item, options)
elif isinstance(item, Meta): # Filters whitespace elif isinstance(item, Meta): # Filters whitespace
yield update_item(item, options) yield _update_item(item, options)
# pylint: disable=locally-disabled, unused-variable # 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 i 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 # 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 i in range(times):
for item in tree: for item in tree:
yield from resolve_item(item, options) yield from _resolve_item(item, options)
def update_options(current: Item, options: dict) -> dict: def _update_options(current: Item, options: dict) -> dict:
"""Update options based on current item""" """Update options based on current item"""
if current.item_type == "change": # Change options if current.item_type == "change": # Change options
options[current.key] = current.value options[current.key] = current.value
@ -322,7 +328,7 @@ class Sequence(Meta):
options[current.key] = current.value options[current.key] = current.value
return options return options
def create_pitch(current: Item, options: dict) -> dict: def _create_pitch(current: Item, options: dict) -> dict:
"""Create pitch based on values and options""" """Create pitch based on values and options"""
if "modifier" in options: if "modifier" in options:
@ -340,17 +346,17 @@ class Sequence(Meta):
if hasattr(current, "octave") and current.octave is not None: if hasattr(current, "octave") and current.octave is not None:
c_octave += current.octave c_octave += current.octave
current_value = current.get_value(options)
note = note_from_pc( note = note_from_pc(
root=options["key"], root=options["key"],
pitch_class=current.get_value(), pitch_class=current_value,
intervals=options["scale"], intervals=options["scale"],
modifier=c_modifier, modifier=c_modifier,
octave=c_octave, octave=c_octave,
) )
new_pitch = Pitch( new_pitch = Pitch(
pitch_class=current.get_value(), pitch_class=current_value,
text=str(current.get_value()), text=str(current_value),
note=note, note=note,
freq=midi_to_freq(note), freq=midi_to_freq(note),
octave=c_octave, octave=c_octave,
@ -359,57 +365,47 @@ class Sequence(Meta):
) )
return new_pitch return new_pitch
def update_chord(current: Chord, options: dict) -> Chord: def _create_chord_from_roman(current: RomanNumeral, options: dict) -> Chord:
"""Update chord based on options"""
pcs = current.pitch_classes
notes = [
pc.set_note(
note_from_pc(options["key"], pc.pitch_class, options["scale"])
)
for pc in pcs
]
current.set_notes(notes)
return current
def create_chord_from_roman(current: RomanNumeral, options: dict) -> Chord:
"""Create chord fom roman numeral""" """Create chord fom roman numeral"""
key = options["key"] key = options["key"]
scale = options["scale"] scale = options["scale"]
pitches = [midi_to_pitch_class(note, key, scale) for note in current.notes] pitch_text=""
chord_notes = [ pitch_classes = []
note_from_pc( chord_notes = []
for note in current.notes:
pitch_dict = midi_to_pitch_class(note, key, scale)
pitch_classes.append(Pitch(pitch_class=pitch_dict["pitch_class"],kwargs=(pitch_dict | options)))
pitch_text+=pitch_dict["text"]
chord_notes.append(note_from_pc(
root=key, root=key,
pitch_class=pitch, pitch_class=pitch_dict["pitch_class"],
intervals=scale, intervals=scale,
modifier=current.modifier if hasattr(current, "modifier") else 0, modifier=pitch_dict.get("modifier",0),
) octave=pitch_dict.get("octave",0)
for pitch in pitches ))
]
chord = Chord( chord = Chord(
text="".join(pitches), pitch_classes=pitches, notes=chord_notes text=pitch_text, pitch_classes=pitch_classes, notes=chord_notes, kwargs=options
) )
return chord return chord
def update_item(item, options): def _update_item(item, options):
"""Update or create new pitch""" """Update or create new pitch"""
if set(("key", "scale")) <= options.keys(): if set(("key", "scale")) <= options.keys():
if isinstance(item,Pitch): if isinstance(item,Pitch):
# TODO: Re-evaluation? item.update_options(options)
# item.check_note(options)
pass
elif isinstance(item, (RandomPitch, RandomInteger)): elif isinstance(item, (RandomPitch, RandomInteger)):
item = create_pitch(item, options) item = _create_pitch(item, options)
elif isinstance(item, Chord): elif isinstance(item, Chord):
item = update_chord(item, options) item.update_notes(options)
elif isinstance(item, RomanNumeral): elif isinstance(item, RomanNumeral):
item = create_chord_from_roman(item, options) item = _create_chord_from_roman(item, options)
item.update_new(options)
return item return item
# Start of the main function: Evaluate and flatten the Ziffers object tree # Start of the main function: Evaluate and flatten the Ziffers object tree
values = self.evaluated_values if eval_tree else self.values values = self.evaluated_values if eval_tree else self.values
for item in values: for item in values:
yield from resolve_item(item, options) yield from _resolve_item(item, options)
def filter(self, keep: tuple): def filter(self, keep: tuple):
"""Filter out items from sequence. """Filter out items from sequence.
@ -551,7 +547,7 @@ class RandomInteger(Item):
self.max = new_max self.max = new_max
# pylint: disable=locally-disabled, unused-argument # pylint: disable=locally-disabled, unused-argument
def get_value(self): def get_value(self, options: dict=None):
"""Evaluate the random value for the generator""" """Evaluate the random value for the generator"""
return random.randint(self.min, self.max) return random.randint(self.min, self.max)

File diff suppressed because it is too large Load Diff

View File

@ -77,9 +77,20 @@ def get_scale(name: str) -> list[int]:
Returns: Returns:
list: List of intervals in the scale list: List of intervals in the scale
""" """
scale = SCALES.get(name.lower().capitalize(), SCALES["Chromatic"]) scale = SCALES.get(name.lower().capitalize(), SCALES["Ionian"])
return list(map(int, str(scale))) return scale
def get_scale_length(name: str) -> int:
"""Get length of the scale
Args:
name (str): Name of the scale
Returns:
int: Length of the scale
"""
scale = SCALES.get(name.lower().capitalize(), SCALES["Ionian"])
return len(scale)
# pylint: disable=locally-disabled, too-many-arguments # pylint: disable=locally-disabled, too-many-arguments
def note_from_pc( def note_from_pc(
@ -186,7 +197,7 @@ def midi_to_tpc(note: int, key: str | int):
_type_: Tonal Pitch Class value for the note _type_: Tonal Pitch Class value for the note
""" """
if isinstance(key, str): if isinstance(key, str):
acc = accidentals_from_note_name(key) acc = accidentals_from_note_name(key[0])
else: else:
acc = accidentals_from_midi_note(key) acc = accidentals_from_midi_note(key)
return (note * 7 + 26 - (11 + acc)) % 12 + (11 + acc) return (note * 7 + 26 - (11 + acc)) % 12 + (11 + acc)