Temporal_Exploration / tests /test_rhythma_layered_audio.py
ciaochris's picture
Render Rhythma sessions as layered ambient audio
c901b5f
import numpy as np
import pytest
from rhythma_engine import RhythmaModulationEngine
def test_render_session_returns_normalized_layered_audio():
engine = RhythmaModulationEngine()
profile = {
"tone_center": 396.0,
"pattern": "calm",
"modulation_type": "sine",
"brightness": 0.25,
"density": 0.45,
"shimmer": 0.12,
"breath_rate": 0.08,
}
audio = engine.render_session(profile, duration=4)
assert len(audio) == engine.sample_rate * 4
assert np.max(np.abs(audio)) <= 0.9 + 1e-9
assert np.max(np.abs(audio[:400])) < np.max(np.abs(audio[2000:2400]))
assert np.max(np.abs(audio[-400:])) < np.max(np.abs(audio[2000:2400]))
def test_render_session_is_not_identical_to_single_layer_base_wave():
engine = RhythmaModulationEngine()
profile = {
"tone_center": 639.0,
"pattern": "focused",
"modulation_type": "sine",
"brightness": 0.4,
"density": 0.35,
"shimmer": 0.18,
"breath_rate": 0.12,
}
session_audio = engine.render_session(profile, duration=3)
legacy_audio = engine.generate_modulated_wave(3)
assert not np.allclose(session_audio[:2000], legacy_audio[:2000])
def test_render_session_keeps_breath_layer_pulse_shaped():
engine = RhythmaModulationEngine()
pulse_profile = {
"tone_center": 528.0,
"pattern": "relaxed",
"modulation_type": "pulse",
"brightness": 0.3,
"density": 0.4,
"shimmer": 0.15,
"breath_rate": 0.1,
}
chirp_profile = dict(pulse_profile, modulation_type="chirp")
pulse_audio = engine.render_session(pulse_profile, duration=2)
chirp_audio = engine.render_session(chirp_profile, duration=2)
assert np.allclose(pulse_audio, chirp_audio)
@pytest.mark.parametrize("duration", [0, -1, 0.5 / RhythmaModulationEngine.SAMPLE_RATE])
def test_render_session_rejects_invalid_duration(duration):
engine = RhythmaModulationEngine()
profile = {
"tone_center": 528.0,
"pattern": "calm",
"modulation_type": "sine",
"brightness": 0.2,
"density": 0.3,
"shimmer": 0.1,
"breath_rate": 0.08,
}
with pytest.raises(ValueError, match="duration must produce at least one sample"):
engine.render_session(profile, duration=duration)