diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ad0d0f..dd1ce07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: env: LILYPOND_VERSION: 2.25.16 - PANG_COMMIT: b5dc6468810789ed8cf79136a7f0a31932904400 + PANG_COMMIT: 63a6320d8bd680feffcb6d22e5743bf9d9ed28af PANG_PATH: /tmp/pang jobs: diff --git a/Makefile b/Makefile index 993b9ba..4ce4dfc 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ check: make black-check make flake8 make isort-check + make mypy test: make black-check diff --git a/minamidera/__init__.py b/minamidera/__init__.py index 102afc8..5864b0b 100644 --- a/minamidera/__init__.py +++ b/minamidera/__init__.py @@ -1,3 +1,3 @@ -from . import library, states +from . import library, soundpointsgenerators, statemapper, statetransition -__all__ = ["library", "states"] +__all__ = ["library", "soundpointsgenerators", "statemapper", "statetransition"] diff --git a/minamidera/library.py b/minamidera/library.py index 5cd711c..03d96ec 100644 --- a/minamidera/library.py +++ b/minamidera/library.py @@ -1,14 +1,7 @@ -import dataclasses -import enum +PITCHES_SETS = [{-6, -5, -4, 0, (1, 2), 3}, {-32, -30, 24, 27}] -AXIS = enum.Enum("AXIS", ["FREQUENCY", "INTENSITY", "DENSITY"]) -FREQUENCY_REGIONS = enum.Enum("FREQUENCY_REGIONS", ["F0", "F1"]) -INTENSITY_REGIONS = enum.Enum("INTENSITY_REGIONS", ["G0", "G1"]) -DENSITY_REGIONS = enum.Enum("DENSITY_REGIONS", ["D0", "D1"]) +INTENSITY_SETS = [{-1, 0}, {-2, 2}] +DENSITY_SETS = [{0.7}, {3.0}] -@dataclasses.dataclass -class State: - frequency_region: FREQUENCY_REGIONS - intensity_region: INTENSITY_REGIONS - density_region: DENSITY_REGIONS +DURATION_SETS = [{0.3}, {1.0}] diff --git a/minamidera/soundpointsgenerators.py b/minamidera/soundpointsgenerators.py new file mode 100644 index 0000000..ed00155 --- /dev/null +++ b/minamidera/soundpointsgenerators.py @@ -0,0 +1,9 @@ +import pang + + +class AtaxicSoundPointsGenerator(pang.SoundPointsGenerator): + def __init__(self, pitches_set, intensity_set, density_set, duration_set): + self.pitches_set = pitches_set + self.intensity_set = intensity_set + self.density_set = density_set + self.duration_set = duration_set diff --git a/minamidera/statemapper.py b/minamidera/statemapper.py index 59ce287..cdffe8c 100644 --- a/minamidera/statemapper.py +++ b/minamidera/statemapper.py @@ -3,9 +3,12 @@ import numpy.typing as npt import pang +from .library import DENSITY_SETS, DURATION_SETS, INTENSITY_SETS, PITCHES_SETS +from .soundpointsgenerators import AtaxicSoundPointsGenerator + def map_state_sequence(state_sequence: Iterable[npt.NDArray]) -> pang.Sequence: - sequence = pang.Sequence([], 0) + sequence = pang.Sequence.empty_sequence() for state in state_sequence: sequence.extend(map_state(state)) return sequence @@ -13,3 +16,22 @@ def map_state_sequence(state_sequence: Iterable[npt.NDArray]) -> pang.Sequence: def map_state(state: npt.NDArray) -> pang.Sequence: raise NotImplementedError + + +def map_state_vector_to_sound_points_generator( + state_vector: npt.NDArray, +) -> pang.SoundPointsGenerator: + if len(state_vector) != 4: + raise ValueError( + f"The shape of the state vector {state_vector} does not match the number of state variables" + ) + if any(state not in (0, 1) for state in state_vector): + raise ValueError( + f"The state vector {state_vector} contains value other than 0 and 1" + ) + return AtaxicSoundPointsGenerator( + PITCHES_SETS[state_vector[0]], + INTENSITY_SETS[state_vector[1]], + DENSITY_SETS[state_vector[2]], + DURATION_SETS[state_vector[3]], + ) diff --git a/minamidera/states.py b/minamidera/statetransition.py similarity index 100% rename from minamidera/states.py rename to minamidera/statetransition.py diff --git a/pyproject.toml b/pyproject.toml index 483b341..2ea5863 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ allow-direct-references = true [project] name = "minamidera" -version = "24.8b0" +version = "24.8b1" authors = [ { name="Tsz Kiu Pang" }, ] diff --git a/tests/test_statemapper.py b/tests/test_statemapper.py new file mode 100644 index 0000000..bfddd3d --- /dev/null +++ b/tests/test_statemapper.py @@ -0,0 +1,33 @@ +import numpy as np +import pang +import pytest + +from minamidera import library, statemapper + + +def test_mapping_empty_sequence(): + assert statemapper.map_state_sequence([]) == pang.Sequence.empty_sequence() + + +def test_mapping_sound_point_generator_from_invalid_state_vector_length(): + with pytest.raises(ValueError) as exception_info: + statemapper.map_state_vector_to_sound_points_generator( + np.array([0, 0, 1, 0, 0]) + ) + assert "does not match" in str(exception_info) + + +def test_mapping_sound_point_generator_from_invalid_state_vector_value(): + with pytest.raises(ValueError) as exception_info: + statemapper.map_state_vector_to_sound_points_generator(np.array([0, 1, 0, 2])) + assert "contains value other than 0 and 1" in str(exception_info) + + +def test_mapping_state_vector_to_sound_point_generator(): + sound_points_generator = statemapper.map_state_vector_to_sound_points_generator( + np.array([0, 1, 0, 1]) + ) + assert sound_points_generator.pitches_set == library.PITCHES_SETS[0] + assert sound_points_generator.intensity_set == library.INTENSITY_SETS[1] + assert sound_points_generator.density_set == library.DENSITY_SETS[0] + assert sound_points_generator.duration_set == library.DURATION_SETS[1] diff --git a/tests/test_states.py b/tests/test_statetransition.py similarity index 77% rename from tests/test_states.py rename to tests/test_statetransition.py index 1efc8bf..f3f03f9 100644 --- a/tests/test_states.py +++ b/tests/test_statetransition.py @@ -1,18 +1,18 @@ import numpy as np -from minamidera import states +from minamidera import statetransition def test_getting_next_state(): state = (0, 0, 1, 1) - next_state = states.get_next_state(state, np.random.default_rng()) + next_state = statetransition.get_next_state(state, np.random.default_rng()) assert len(next_state) == 4 def test_generating_state_sequence(): initial_state = (0, 0, 0, 0) sequence_length = 100 - state_sequence = states.generate_state_sequence( + state_sequence = statetransition.generate_state_sequence( initial_state, sequence_length, np.random.default_rng() ) assert len(state_sequence) == sequence_length @@ -23,15 +23,16 @@ def test_generating_state_sequences(): number_of_state_vectors = 100 initial_states = tuple(initial_state for _ in range(number_of_state_vectors)) sequence_length = 10 - state_sequences = states.generate_state_sequences( + state_sequences = statetransition.generate_state_sequences( initial_states, sequence_length, np.random.default_rng() ) assert len(state_sequences) == number_of_state_vectors assert all([len(sequence) == sequence_length for sequence in state_sequences]) + # TODO: assert statistical properties perhaps def test_enumerating_state_vectors(): - assert states.enumerate_state_vectors(4, 2) == { + assert statetransition.enumerate_state_vectors(4, 2) == { 0: (0, 0, 0, 0), 1: (0, 0, 0, 1), 2: (0, 0, 1, 0),