Added measures and fixed some bugs
This commit is contained in:
@ -72,7 +72,8 @@ def test_pitch_octaves(pattern: str, expected: list):
|
|||||||
[
|
[
|
||||||
("w [1 [2 3]]", [0.5, 0.25, 0.25]),
|
("w [1 [2 3]]", [0.5, 0.25, 0.25]),
|
||||||
("1.0 [1 [2 3]] 4 [3 [4 5]]", [0.5, 0.25, 0.25, 1.0, 0.5, 0.25, 0.25]),
|
("1.0 [1 [2 3]] 4 [3 [4 5]]", [0.5, 0.25, 0.25, 1.0, 0.5, 0.25, 0.25]),
|
||||||
("0.5 (0 0.25 3)+1", [0.5, 0.25])
|
("0.5 (0 0.25 3)+1", [0.5, 0.25]),
|
||||||
|
("[0 2 <2 8>:2 4] 0", [0.05, 0.05, 0.05, 0.05, 0.05, 0.25])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_subdivisions(pattern: str, expected: list):
|
def test_subdivisions(pattern: str, expected: list):
|
||||||
@ -82,13 +83,14 @@ def test_subdivisions(pattern: str, expected: list):
|
|||||||
"pattern,expected",
|
"pattern,expected",
|
||||||
[
|
[
|
||||||
("[: 1 [: 2 :] 3 :]", [62, 64, 64, 65, 62, 64, 64, 65]),
|
("[: 1 [: 2 :] 3 :]", [62, 64, 64, 65, 62, 64, 64, 65]),
|
||||||
("(: 1 (: 2 :) 3 :)", [62, 64, 64, 65, 62, 64, 64, 65])
|
("(: 1 (: 2 :) 3 :)", [62, 64, 64, 65, 62, 64, 64, 65]),
|
||||||
|
("(1 2:2 3):2", [62, 64, 64, 65, 62, 64, 64, 65]),
|
||||||
|
("1:4",[62,62,62,62])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_repeats(pattern: str, expected: list):
|
def test_repeats(pattern: str, expected: list):
|
||||||
assert collect(zparse(pattern),len(expected)*2,"note") == expected*2
|
assert collect(zparse(pattern),len(expected)*2,"note") == expected*2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"pattern,expected",
|
"pattern,expected",
|
||||||
[
|
[
|
||||||
@ -106,3 +108,20 @@ def test_looping_durations(pattern: str, expected: list):
|
|||||||
durations.append(parsed[i].duration)
|
durations.append(parsed[i].duration)
|
||||||
assert durations == expected
|
assert durations == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern,expected",
|
||||||
|
[
|
||||||
|
("e 1 | 3 | h 3 | e3 | 4", [0.125,0.25,0.5,0.125,0.25])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_measure_durations(pattern: str, expected: list):
|
||||||
|
assert collect(zparse(pattern),len(expected)*2,"duration") == expected*2
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern,expected",
|
||||||
|
[
|
||||||
|
("^ 1 | _ 3 | ^3 | 3 | _4", [1,-1,1,0,-1])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_measure_octaves(pattern: str, expected: list):
|
||||||
|
assert collect(zparse(pattern),len(expected)*2,"octave") == expected*2
|
||||||
|
|||||||
@ -58,6 +58,7 @@ class Item(Meta):
|
|||||||
"""Class for all Ziffers text based items"""
|
"""Class for all Ziffers text based items"""
|
||||||
|
|
||||||
text: str = field(default=None)
|
text: str = field(default=None)
|
||||||
|
measure: int = field(default=0, init=False)
|
||||||
|
|
||||||
def get_updated_item(self, options: dict):
|
def get_updated_item(self, options: dict):
|
||||||
"""Get updated item with replaced options
|
"""Get updated item with replaced options
|
||||||
@ -135,6 +136,20 @@ class Event(Item):
|
|||||||
class Rest(Event):
|
class Rest(Event):
|
||||||
"""Class for rests"""
|
"""Class for rests"""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Measure(Item):
|
||||||
|
""" Class for measures/bars. Used to reset default options. """
|
||||||
|
text: str = field(default="|", init=False)
|
||||||
|
|
||||||
|
def reset_options(self, options: dict):
|
||||||
|
"""Reset options when measure changes"""
|
||||||
|
next_measure = options.get("measure", 0)+1
|
||||||
|
start_options = options["start_options"].copy()
|
||||||
|
options.clear()
|
||||||
|
options.update(start_options)
|
||||||
|
options["measure"] = next_measure
|
||||||
|
options["start_options"] = start_options.copy()
|
||||||
|
self.measure = next_measure
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class Pitch(Event):
|
class Pitch(Event):
|
||||||
@ -211,25 +226,6 @@ class Pitch(Event):
|
|||||||
"""
|
"""
|
||||||
return self.pitch_class
|
return self.pitch_class
|
||||||
|
|
||||||
def get_notes(self) -> int:
|
|
||||||
"""Return notes"""
|
|
||||||
return self.note
|
|
||||||
|
|
||||||
def get_octaves(self) -> int:
|
|
||||||
"""Return octave"""
|
|
||||||
return self.octave
|
|
||||||
|
|
||||||
def get_beats(self) -> float:
|
|
||||||
"""Return beats"""
|
|
||||||
return self.beat
|
|
||||||
|
|
||||||
def get_durations(self) -> float:
|
|
||||||
"""Return duration"""
|
|
||||||
return self.duration
|
|
||||||
|
|
||||||
def get_freqs(self) -> float:
|
|
||||||
"""Return frequencies"""
|
|
||||||
return self.freq
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
@ -261,13 +257,13 @@ class Chord(Event):
|
|||||||
"""Class for chords"""
|
"""Class for chords"""
|
||||||
|
|
||||||
pitch_classes: list[Pitch] = field(default=None)
|
pitch_classes: list[Pitch] = field(default=None)
|
||||||
notes: list[int] = field(default=None)
|
note: list[int] = field(default=None)
|
||||||
inversions: int = field(default=None)
|
inversions: int = field(default=None)
|
||||||
pitches: list[int] = field(default=None, init=False)
|
pitch_class: list[int] = field(default=None, init=False)
|
||||||
freqs: list[float] = field(default=None, init=False)
|
freq: list[float] = field(default=None, init=False)
|
||||||
octaves: list[int] = field(default=None, init=False)
|
octave: list[int] = field(default=None, init=False)
|
||||||
durations: list[float] = field(default=None, init=False)
|
duration: list[float] = field(default=None, init=False)
|
||||||
beats: list[float] = field(default=None, init=False)
|
beat: list[float] = field(default=None, init=False)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.inversions is not None:
|
if self.inversions is not None:
|
||||||
@ -275,7 +271,7 @@ class Chord(Event):
|
|||||||
|
|
||||||
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.note = notes
|
||||||
|
|
||||||
def invert(self, value: int):
|
def invert(self, value: int):
|
||||||
"""Chord inversion"""
|
"""Chord inversion"""
|
||||||
@ -311,36 +307,12 @@ class Chord(Event):
|
|||||||
durations.append(pitch.duration)
|
durations.append(pitch.duration)
|
||||||
beats.append(pitch.beat)
|
beats.append(pitch.beat)
|
||||||
|
|
||||||
self.pitches = pitches
|
self.pitch = pitches
|
||||||
self.notes = notes
|
self.note = notes
|
||||||
self.freqs = freqs
|
self.freq = freqs
|
||||||
self.octaves = octaves
|
self.octave = octaves
|
||||||
self.duration = durations
|
self.duration = durations
|
||||||
self.beats = beats
|
self.beat = beats
|
||||||
|
|
||||||
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) -> list:
|
|
||||||
"""Return octave"""
|
|
||||||
return self.octaves
|
|
||||||
|
|
||||||
def get_beats(self) -> float:
|
|
||||||
"""Return beats"""
|
|
||||||
return self.beats
|
|
||||||
|
|
||||||
def get_durations(self) -> float:
|
|
||||||
"""Return duration"""
|
|
||||||
return self.durations
|
|
||||||
|
|
||||||
def get_freqs(self) -> float:
|
|
||||||
"""Return frequencies"""
|
|
||||||
return self.freqs
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
@ -452,11 +424,13 @@ class Sequence(Meta):
|
|||||||
elif isinstance(item, RepeatedSequence):
|
elif isinstance(item, RepeatedSequence):
|
||||||
item.evaluate_values(options)
|
item.evaluate_values(options)
|
||||||
repeats = item.repeats.get_value(options)
|
repeats = item.repeats.get_value(options)
|
||||||
repeats = _resolve_repeat_value(repeats)
|
if not isinstance(repeats, int):
|
||||||
|
repeats = _resolve_repeat_value(repeats)
|
||||||
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(options)
|
repeats = item.repeats.get_value(options)
|
||||||
repeats = _resolve_repeat_value(repeats)
|
if not isinstance(repeats, int):
|
||||||
|
repeats = _resolve_repeat_value(repeats)
|
||||||
yield from _generative_repeat(item, repeats, options)
|
yield from _generative_repeat(item, repeats, options)
|
||||||
elif isinstance(item, Subdivision):
|
elif isinstance(item, Subdivision):
|
||||||
item.evaluate_values(options)
|
item.evaluate_values(options)
|
||||||
@ -484,6 +458,8 @@ class Sequence(Meta):
|
|||||||
yield from _euclidean_items(item, options)
|
yield from _euclidean_items(item, options)
|
||||||
elif isinstance(item, Modification):
|
elif isinstance(item, Modification):
|
||||||
options = _parse_options(item, options)
|
options = _parse_options(item, options)
|
||||||
|
elif isinstance(item, Measure):
|
||||||
|
item.reset_options(options)
|
||||||
elif isinstance(item, Meta): # Filters whitespace
|
elif isinstance(item, Meta): # Filters whitespace
|
||||||
yield _update_item(item, options)
|
yield _update_item(item, options)
|
||||||
|
|
||||||
@ -492,8 +468,8 @@ class Sequence(Meta):
|
|||||||
item = item.get_value(options)
|
item = item.get_value(options)
|
||||||
if isinstance(item, Pitch):
|
if isinstance(item, Pitch):
|
||||||
return item.get_value(options)
|
return item.get_value(options)
|
||||||
if not isinstance(item, Integer):
|
if isinstance(item, Integer):
|
||||||
return 2
|
return item.get_value(options)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def _update_item(item, options):
|
def _update_item(item, options):
|
||||||
@ -548,9 +524,6 @@ class Sequence(Meta):
|
|||||||
options[current.key] = current.value
|
options[current.key] = current.value
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def _create_pitch_without_note(current: Item, options: dict) -> Pitch:
|
|
||||||
return Pitch(pitch_class=current.get_value(options))
|
|
||||||
|
|
||||||
def _create_pitch(current: Item, options: dict) -> Pitch:
|
def _create_pitch(current: Item, options: dict) -> Pitch:
|
||||||
"""Create pitch based on values and options"""
|
"""Create pitch based on values and options"""
|
||||||
|
|
||||||
@ -623,7 +596,7 @@ class Sequence(Meta):
|
|||||||
chord = Chord(
|
chord = Chord(
|
||||||
text=pitch_text,
|
text=pitch_text,
|
||||||
pitch_classes=pitch_classes,
|
pitch_classes=pitch_classes,
|
||||||
notes=chord_notes,
|
note=chord_notes,
|
||||||
kwargs=options,
|
kwargs=options,
|
||||||
inversions=current.inversions,
|
inversions=current.inversions,
|
||||||
)
|
)
|
||||||
@ -691,11 +664,13 @@ class Ziffers(Sequence):
|
|||||||
self.options = DEFAULT_OPTIONS.copy()
|
self.options = DEFAULT_OPTIONS.copy()
|
||||||
|
|
||||||
self.start_options = self.options.copy()
|
self.start_options = self.options.copy()
|
||||||
|
self.options["start_options"] = self.start_options
|
||||||
self.init_tree(self.options)
|
self.init_tree(self.options)
|
||||||
|
|
||||||
def re_eval(self):
|
def re_eval(self):
|
||||||
"""Re-evaluate the iterator"""
|
"""Re-evaluate the iterator"""
|
||||||
self.options = self.start_options.copy()
|
self.options = self.start_options.copy()
|
||||||
|
self.options["start_options"] = self.start_options
|
||||||
self.init_tree(self.options)
|
self.init_tree(self.options)
|
||||||
|
|
||||||
def init_tree(self, options):
|
def init_tree(self, options):
|
||||||
@ -743,7 +718,7 @@ class Ziffers(Sequence):
|
|||||||
def pitch_classes(self) -> list[int]:
|
def pitch_classes(self) -> list[int]:
|
||||||
"""Return list of pitch classes as ints"""
|
"""Return list of pitch classes as ints"""
|
||||||
return [
|
return [
|
||||||
val.get_pitches()
|
val.pitch_class
|
||||||
for val in self.evaluated_values
|
for val in self.evaluated_values
|
||||||
if isinstance(val, (Pitch, Chord))
|
if isinstance(val, (Pitch, Chord))
|
||||||
]
|
]
|
||||||
@ -751,7 +726,7 @@ class Ziffers(Sequence):
|
|||||||
def notes(self) -> list[int]:
|
def notes(self) -> list[int]:
|
||||||
"""Return list of midi notes"""
|
"""Return list of midi notes"""
|
||||||
return [
|
return [
|
||||||
val.get_notes()
|
val.note
|
||||||
for val in self.evaluated_values
|
for val in self.evaluated_values
|
||||||
if isinstance(val, (Pitch, Chord))
|
if isinstance(val, (Pitch, Chord))
|
||||||
]
|
]
|
||||||
@ -759,7 +734,7 @@ class Ziffers(Sequence):
|
|||||||
def durations(self) -> list[float]:
|
def durations(self) -> list[float]:
|
||||||
"""Return list of pitch durations as floats"""
|
"""Return list of pitch durations as floats"""
|
||||||
return [
|
return [
|
||||||
val.get_durations()
|
val.duration
|
||||||
for val in self.evaluated_values
|
for val in self.evaluated_values
|
||||||
if isinstance(val, Event)
|
if isinstance(val, Event)
|
||||||
]
|
]
|
||||||
@ -767,7 +742,7 @@ class Ziffers(Sequence):
|
|||||||
def beats(self) -> list[float]:
|
def beats(self) -> list[float]:
|
||||||
"""Return list of pitch durations as floats"""
|
"""Return list of pitch durations as floats"""
|
||||||
return [
|
return [
|
||||||
val.get_beats() for val in self.evaluated_values if isinstance(val, Event)
|
val.beat for val in self.evaluated_values if isinstance(val, Event)
|
||||||
]
|
]
|
||||||
|
|
||||||
def pairs(self) -> list[tuple]:
|
def pairs(self) -> list[tuple]:
|
||||||
@ -781,7 +756,7 @@ class Ziffers(Sequence):
|
|||||||
def octaves(self) -> list[int]:
|
def octaves(self) -> list[int]:
|
||||||
"""Return list of octaves"""
|
"""Return list of octaves"""
|
||||||
return [
|
return [
|
||||||
val.get_octaves()
|
val.octave
|
||||||
for val in self.evaluated_values
|
for val in self.evaluated_values
|
||||||
if isinstance(val, (Pitch, Chord))
|
if isinstance(val, (Pitch, Chord))
|
||||||
]
|
]
|
||||||
@ -789,7 +764,7 @@ class Ziffers(Sequence):
|
|||||||
def freqs(self) -> list[int]:
|
def freqs(self) -> list[int]:
|
||||||
"""Return list of octaves"""
|
"""Return list of octaves"""
|
||||||
return [
|
return [
|
||||||
val.get_freqs()
|
val.freq
|
||||||
for val in self.evaluated_values
|
for val in self.evaluated_values
|
||||||
if isinstance(val, (Pitch, Chord))
|
if isinstance(val, (Pitch, Chord))
|
||||||
]
|
]
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class ZiffersMusic21(converter.subConverters.SubConverter):
|
|||||||
elif isinstance(item, Rest):
|
elif isinstance(item, Rest):
|
||||||
m_item = note.Rest(item.duration * 4)
|
m_item = note.Rest(item.duration * 4)
|
||||||
elif isinstance(item, Chord):
|
elif isinstance(item, Chord):
|
||||||
m_item = chord.Chord(item.notes)
|
m_item = chord.Chord(item.note)
|
||||||
m_item.duration.quarterLength = item.duration * 4
|
m_item.duration.quarterLength = item.duration * 4
|
||||||
note_stream.append(m_item)
|
note_stream.append(m_item)
|
||||||
self.stream = note_stream.makeMeasures()
|
self.stream = note_stream.makeMeasures()
|
||||||
|
|||||||
@ -46,7 +46,8 @@ DEFAULT_OPTIONS = MappingProxyType({
|
|||||||
"octave": 0,
|
"octave": 0,
|
||||||
"duration": 0.25,
|
"duration": 0.25,
|
||||||
"key": "C4",
|
"key": "C4",
|
||||||
"scale": "IONIAN"
|
"scale": "IONIAN",
|
||||||
|
"measure": 0
|
||||||
})
|
})
|
||||||
|
|
||||||
OPERATORS = MappingProxyType({
|
OPERATORS = MappingProxyType({
|
||||||
|
|||||||
@ -29,6 +29,7 @@ from .classes import (
|
|||||||
RepeatedSequence,
|
RepeatedSequence,
|
||||||
VariableAssignment,
|
VariableAssignment,
|
||||||
Variable,
|
Variable,
|
||||||
|
Measure
|
||||||
)
|
)
|
||||||
from .common import flatten, sum_dict
|
from .common import flatten, sum_dict
|
||||||
from .defaults import DEFAULT_DURS, OPERATORS
|
from .defaults import DEFAULT_DURS, OPERATORS
|
||||||
@ -56,6 +57,10 @@ class ZiffersTransformer(Transformer):
|
|||||||
return Rest(text=text_prefix + "r", local_options=prefixes)
|
return Rest(text=text_prefix + "r", local_options=prefixes)
|
||||||
return Rest(text="r")
|
return Rest(text="r")
|
||||||
|
|
||||||
|
def measure(self, items):
|
||||||
|
"""Return new measure"""
|
||||||
|
return Measure()
|
||||||
|
|
||||||
def random_integer(self, items) -> RandomInteger:
|
def random_integer(self, items) -> RandomInteger:
|
||||||
"""Parses random integer syntax"""
|
"""Parses random integer syntax"""
|
||||||
if len(items) > 1:
|
if len(items) > 1:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Root for the rules
|
// Root for the rules
|
||||||
?root: sequence -> start
|
?root: sequence -> start
|
||||||
sequence: (pitch_class | repeat_item | assignment | variable | 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)*
|
sequence: (pitch_class | repeat_item | assignment | variable | rest | dur_change | oct_mod | oct_change | WS | measure | 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,8 @@
|
|||||||
octave: /[_^]+/
|
octave: /[_^]+/
|
||||||
modifier: /[#b]/
|
modifier: /[#b]/
|
||||||
|
|
||||||
|
measure: "|"
|
||||||
|
|
||||||
// Variable assignment
|
// Variable assignment
|
||||||
assignment: variable ass_op (list | pitch_class | random_integer | random_pitch | cycle | list_op | repeat_item)
|
assignment: variable ass_op (list | pitch_class | random_integer | random_pitch | cycle | list_op | repeat_item)
|
||||||
ass_op: /[=~]/
|
ass_op: /[=~]/
|
||||||
|
|||||||
Reference in New Issue
Block a user