Added rest and infinite indexing

Preliminary support for rest: q 1 r 3 er 4 etc. Currently supports only single character prefixes like: qr er.
This commit is contained in:
2023-02-15 23:14:53 +02:00
parent 10f66d0027
commit f6c6497319
3 changed files with 74 additions and 36 deletions

View File

@ -88,6 +88,9 @@ class Event(Item):
duration: float = field(default=None) duration: float = field(default=None)
@dataclass
class Rest(Event):
"""Class for rests"""
@dataclass(kw_only=True) @dataclass(kw_only=True)
class Pitch(Event): class Pitch(Event):
@ -136,7 +139,6 @@ class Pitch(Event):
if edit: if edit:
self.update_note(True) self.update_note(True)
def set_note(self, note: int) -> int: def set_note(self, note: int) -> int:
"""Sets a note for the pitch and returns the note. """Sets a note for the pitch and returns the note.
@ -175,7 +177,9 @@ class RandomPitch(Event):
Returns: Returns:
int: Returns random pitch int: Returns random pitch
""" """
return random.randint(0, get_scale_length(options.get("scale","Major")) if options else 9) return random.randint(
0, get_scale_length(options.get("scale", "Major")) if options else 9
)
@dataclass(kw_only=True) @dataclass(kw_only=True)
@ -205,6 +209,7 @@ class Chord(Event):
notes.append(pitch.note) notes.append(pitch.note)
self.notes = notes self.notes = notes
@dataclass(kw_only=True) @dataclass(kw_only=True)
class RomanNumeral(Event): class RomanNumeral(Event):
"""Class for roman numbers""" """Class for roman numbers"""
@ -257,7 +262,7 @@ class Sequence(Meta):
self.text = self.__collect_text() self.text = self.__collect_text()
def __getitem__(self, index): def __getitem__(self, index):
return self.evaluated_values[index % len(self.evaluated_values)] return self.values[index]
def update_values(self, new_values): def update_values(self, new_values):
"""Update value attributes from dict""" """Update value attributes from dict"""
@ -317,6 +322,7 @@ class Sequence(Meta):
for item in tree: for item in tree:
yield from _resolve_item(item, options) yield from _resolve_item(item, options)
# TODO: Refactor types to isinstance?
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
@ -374,18 +380,28 @@ class Sequence(Meta):
chord_notes = [] chord_notes = []
for note in current.notes: for note in current.notes:
pitch_dict = midi_to_pitch_class(note, key, scale) pitch_dict = midi_to_pitch_class(note, key, scale)
pitch_classes.append(Pitch(pitch_class=pitch_dict["pitch_class"],kwargs=(pitch_dict | options))) pitch_classes.append(
Pitch(
pitch_class=pitch_dict["pitch_class"],
kwargs=(pitch_dict | options),
)
)
pitch_text += pitch_dict["text"] pitch_text += pitch_dict["text"]
chord_notes.append(note_from_pc( chord_notes.append(
note_from_pc(
root=key, root=key,
pitch_class=pitch_dict["pitch_class"], pitch_class=pitch_dict["pitch_class"],
intervals=scale, intervals=scale,
modifier=pitch_dict.get("modifier", 0), modifier=pitch_dict.get("modifier", 0),
octave=pitch_dict.get("octave",0) octave=pitch_dict.get("octave", 0),
)) )
)
chord = Chord( chord = Chord(
text=pitch_text, pitch_classes=pitch_classes, notes=chord_notes, kwargs=options text=pitch_text,
pitch_classes=pitch_classes,
notes=chord_notes,
kwargs=options,
) )
return chord return chord
@ -394,6 +410,9 @@ class Sequence(Meta):
if set(("key", "scale")) <= options.keys(): if set(("key", "scale")) <= options.keys():
if isinstance(item, Pitch): if isinstance(item, Pitch):
item.update_options(options) item.update_options(options)
item.update_note()
if isinstance(item,Rest):
item.update_options(options)
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):
@ -420,7 +439,6 @@ class Sequence(Meta):
self, values=[item for item in self.values if isinstance(item, keep)] self, values=[item for item in self.values if isinstance(item, keep)]
) )
@dataclass(kw_only=True) @dataclass(kw_only=True)
class Ziffers(Sequence): class Ziffers(Sequence):
"""Main class for holding options and the current state""" """Main class for holding options and the current state"""
@ -430,6 +448,9 @@ class Ziffers(Sequence):
iterator = None iterator = None
current: Item = field(default=None) current: Item = field(default=None)
def __getitem__(self, index):
return self.evaluated_values[index % len(self.evaluated_values)]
def __iter__(self): def __iter__(self):
return self return self

View File

@ -8,6 +8,7 @@ from .classes import (
OctaveChange, OctaveChange,
OctaveAdd, OctaveAdd,
Pitch, Pitch,
Rest,
RandomPitch, RandomPitch,
RandomPercent, RandomPercent,
Chord, Chord,
@ -37,19 +38,31 @@ from .scale import parse_roman, chord_from_roman_numeral
class ZiffersTransformer(Transformer): class ZiffersTransformer(Transformer):
"""Rules for transforming Ziffers expressions into tree.""" """Rules for transforming Ziffers expressions into tree."""
def __init__(self, options: Optional[dict] = None):
super().__init__()
self.options = options
def start(self, items) -> Ziffers: def start(self, items) -> Ziffers:
"""Root for the rules""" """Root for the rules"""
# seq = Sequence(values=items[0])
return Ziffers(values=items[0], options={}) return Ziffers(values=items[0], options={})
def sequence(self, items): def sequence(self, items):
"""Flatten sequence""" """Flatten sequence"""
return flatten(items) return flatten(items)
def rest(self, items):
"""Return duration event"""
if len(items)>0:
chars = items[0]
val = DEFAULT_DURS[chars[0]]
# TODO: Add support for dots
#if len(chars)>1:
# dots = len(chars)-1
# val = val * (2.0 - (1.0 / (2 * dots)))
return Rest(text=chars+"r", duration=val)
return Rest(text="r")
return Rest(text=chars+"r", duration=val)
def rest_duration(self,items):
return items[0].value
def random_integer(self, item) -> RandomInteger: def random_integer(self, item) -> RandomInteger:
"""Parses random integer syntax""" """Parses random integer syntax"""
val = item[0][1:-1].split(",") val = item[0][1:-1].split(",")

View File

@ -1,6 +1,6 @@
// Root for the rules // Root for the rules
?root: sequence -> start ?root: sequence -> start
sequence: (pitch_class | dur_change | oct_mod | oct_change | WS | chord | named_roman | cycle | random_integer | random_pitch | random_percent | range | list | repeated_list | lisp_operation | list_op | subdivision | eval | euclid | repeat)* sequence: (pitch_class | rest | dur_change | oct_mod | oct_change | WS | chord | named_roman | cycle | random_integer | random_pitch | random_percent | range | list | repeated_list | lisp_operation | list_op | subdivision | eval | euclid | repeat)*
// Pitch classes // Pitch classes
pitch_class: prefix* pitch pitch_class: prefix* pitch
@ -11,6 +11,18 @@
octave: /[_^]+/ octave: /[_^]+/
modifier: /[#b]/ modifier: /[#b]/
// Durations
// TODO: Refactor dchar as: /([mklpdcwyhnqaefsxtgujzo](\.)*)(?=\d)/
duration_chars: dotted_dur+
dotted_dur: dchar dot*
decimal: /-?[0-9]+\.[0-9]+/
dchar: /[mklpdcwyhnqaefsxtgujzo]/
dot: "."
rest: rest_duration? "r"
// TODO: Refactor (\.)* when other durchars uses lookaheads
rest_duration: /([mklpdcwyhnqaefsxtgujzo])(?=r)/
// Chords // Chords
chord: pitch_class pitch_class+ chord: pitch_class pitch_class+
named_roman: roman_number (("^" chord_name))? // TODO: Add | ("+" number) named_roman: roman_number (("^" chord_name))? // TODO: Add | ("+" number)
@ -34,22 +46,14 @@
operator: /([\+\-\*\/%]|<<|>>)/ operator: /([\+\-\*\/%]|<<|>>)/
// Euclidean cycles // Euclidean cycles
// TODO: Support randomization etc.
//euclid_operator: (">" | "<") number "," number ["," number] (">" | "<")
euclid: list euclid_operator list? euclid: list euclid_operator list?
?euclid_operator: /<[0-9]+,[0-9]+(,[0-9])?>/ ?euclid_operator: /<[0-9]+,[0-9]+(,[0-9])?>/
// TODO: Support randomization etc.
//euclid_operator: (">" | "<") number "," number ["," number] (">" | "<")
// Lisp like list operation // Lisp like list operation
lisp_operation: "(" operator WS sequence ")" lisp_operation: "(" operator WS sequence ")"
// Durations
duration_chars: dotted_dur+
dotted_dur: dchar dot*
decimal: /-?[0-9]+\.[0-9]+/
dchar: /[mklpdcwyhnqaefsxtgujzo]/
dot: "."
// Subdivision // Subdivision
subdivision: "[" subitems "]" subdivision: "[" subitems "]"
subitems: (pitch_class | WS | chord | cycle | subdivision)* subitems: (pitch_class | WS | chord | cycle | subdivision)*
@ -59,7 +63,7 @@
oct_change: escaped_octave WS oct_change: escaped_octave WS
dur_change: (decimal | char_change) dur_change: (decimal | char_change)
char_change: dchar_not_prefix+ char_change: dchar_not_prefix+
dchar_not_prefix: /([mklpdcwyhnqaefsxtgujzo](\.)*)(?!\d)/ dchar_not_prefix: /([mklpdcwyhnqaefsxtgujzo](\.)*)(?![\dr])/
// Generative rules // Generative rules
random_integer: /\(-?[0-9]+,-?[0-9]+\)/ random_integer: /\(-?[0-9]+,-?[0-9]+\)/