diff --git a/tests/test_singular_02.py b/tests/test_singular_02.py index aaadca1..1115530 100644 --- a/tests/test_singular_02.py +++ b/tests/test_singular_02.py @@ -16,7 +16,8 @@ def test_can_parse(): "q 2 <3 343>", "q (2 <3 343 (3 4)>)", "? 1 2", - "(? 2 ? 4)+(1,4)" + "(? 2 ? 4)+(1,4)", + "(1 2 <2 3>)+(0 1 2)" ] results = [] for expression in expressions: @@ -43,7 +44,6 @@ def test_can_parse(): def test_parsing_text(pattern: str): assert zparse(pattern).text == pattern - @pytest.mark.parametrize( "pattern,expected", [ @@ -159,6 +159,15 @@ def test_rest(pattern: str, expected: list): def test_ranges(pattern: str, expected: list): assert get_items(zparse(pattern),len(expected)*2,"note") == expected*2 +@pytest.mark.parametrize( + "pattern,expected", + [ + ("0 1 2 3", [None, 60, 62, 64]) + ] +) +def test_degree_notation(pattern: str, expected: list): + assert get_items(zparse(pattern, degrees=True),len(expected)*2,"note") == expected*2 + @pytest.mark.parametrize( "pattern,expected", [ diff --git a/ziffers/classes/items.py b/ziffers/classes/items.py index a4e405b..418253c 100644 --- a/ziffers/classes/items.py +++ b/ziffers/classes/items.py @@ -198,6 +198,7 @@ class Pitch(Event): key: str = field(default=None) scale: str | list = field(default=None) freq: float = field(default=None) + degrees: bool = field(default=None) def __post_init__(self): super().__post_init__() @@ -246,6 +247,7 @@ class Pitch(Event): intervals=self.scale, modifier=self.modifier if self.modifier is not None else 0, octave=self.octave if self.octave is not None else 0, + degrees=self.degrees ) self.pitch_bend = pitch_bend self.freq = midi_to_freq(note) @@ -598,6 +600,8 @@ class Cyclic(Item): """Get the value for the current cycle""" value = self.values[self.cycle % len(self.values)] self.cycle += 1 + if options: + value.update_options(options) return value diff --git a/ziffers/classes/sequences.py b/ziffers/classes/sequences.py index cd6b8f6..2489a11 100644 --- a/ziffers/classes/sequences.py +++ b/ziffers/classes/sequences.py @@ -124,6 +124,8 @@ def resolve_item(item: Meta, options: dict): update_modifications(item, options) elif isinstance(item, Measure): item.reset_options(options) + elif options["degrees"] is True and isinstance(item, Pitch) and item.pitch_class is 0: + yield Rest(text="r", kwargs=options) elif isinstance(item, Meta): # Filters whitespace yield update_item(item, options) @@ -208,6 +210,7 @@ def create_pitch(current: Item, options: dict) -> Pitch: intervals=merged_options["scale"], modifier=c_modifier, octave=c_octave, + degrees=merged_options["degrees"] ) new_pitch = Pitch( pitch_class=current_value, @@ -382,7 +385,7 @@ class ListOperation(Sequence): else: flattened_list.append(_filter_operation(item, options)) elif isinstance(item, Cyclic): - value = item.get_value() + value = item.get_value(options) if isinstance(value, Sequence): flattened_list.extend(_filter_operation(value, options)) elif isinstance(value, (Event, RandomInteger, Integer)): diff --git a/ziffers/defaults.py b/ziffers/defaults.py index 92ca46b..2e349cd 100644 --- a/ziffers/defaults.py +++ b/ziffers/defaults.py @@ -47,7 +47,8 @@ DEFAULT_OPTIONS = MappingProxyType({ "duration": 0.25, "key": "C4", "scale": "IONIAN", - "measure": 0 + "measure": 0, + "degrees": False }) OPERATORS = MappingProxyType({ diff --git a/ziffers/scale.py b/ziffers/scale.py index 3ef1836..353a5fb 100644 --- a/ziffers/scale.py +++ b/ziffers/scale.py @@ -149,7 +149,6 @@ def get_scale_length(scale: str) -> int: return len(SCALES.get(scale.lower().capitalize(), SCALES["Ionian"])) - # pylint: disable=locally-disabled, too-many-arguments def note_from_pc( root: int | str, @@ -157,6 +156,7 @@ def note_from_pc( intervals: str | tuple[int | float], octave: int = 0, modifier: int = 0, + degrees: bool = False ) -> int: """Resolve a pitch class into a note from a scale @@ -173,6 +173,7 @@ def note_from_pc( """ # Initialization + pitch_class = pitch_class-1 if degrees and pitch_class>0 else pitch_class root = note_name_to_midi(root) if isinstance(root, str) else root intervals = get_scale(intervals) if isinstance(intervals, str) else intervals scale_length = len(intervals)