feat: professional reggaeton production engine — 7 SDD changes, 302 tests
- section-energy: track activity matrix + volume/velocity multipliers per section - smart-chords: ChordEngine with voice leading, inversions, 4 emotion modes - hook-melody: melody engine with hook/stabs/smooth styles, call-and-response - mix-calibration: Calibrator module (LUFS volumes, HPF/LPF, stereo, sends, master) - transitions-fx: FX track with risers/impacts/sweeps at section boundaries - sidechain: MIDI CC11 bass ducking on kick hits via DrumLoopAnalyzer - presets-pack: role-aware plugin presets (Serum/Decapitator/Omnisphere per role) Full SDD pipeline (propose→spec→design→tasks→apply→verify) for all 7 changes. 302/302 tests passing.
This commit is contained in:
194
tests/test_preset_transform.py
Normal file
194
tests/test_preset_transform.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""Tests for role-aware preset system (presets-pack).
|
||||
|
||||
Covers PresetTransformer, role-aware PLUGIN_PRESETS lookup,
|
||||
and thread-through via make_plugin → PluginDef.role → _build_plugin.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from src.reaper_builder.preset_transformer import PresetTransformer
|
||||
from src.reaper_builder import PLUGIN_PRESETS, PLUGIN_REGISTRY
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PresetTransformer unit tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestPresetTransformer:
|
||||
"""PresetTransformer derives role-specific preset data."""
|
||||
|
||||
def test_derive_serum_returns_list(self):
|
||||
"""derive() for Serum_2 returns a list of chunks."""
|
||||
default = PLUGIN_PRESETS.get(("Serum_2", ""))
|
||||
assert default is not None, "Serum_2 default preset must exist"
|
||||
result = PresetTransformer.derive("Serum_2", default, "bass")
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == len(default)
|
||||
assert all(isinstance(c, str) for c in result)
|
||||
|
||||
def test_derive_decapitator_returns_list(self):
|
||||
"""derive() for Decapitator returns a list of chunks."""
|
||||
default = PLUGIN_PRESETS.get(("Decapitator", ""))
|
||||
assert default is not None, "Decapitator default preset must exist"
|
||||
result = PresetTransformer.derive("Decapitator", default, "drums")
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == len(default)
|
||||
|
||||
def test_derive_omnisphere_returns_list(self):
|
||||
"""derive() for Omnisphere returns a list of chunks."""
|
||||
default = PLUGIN_PRESETS.get(("Omnisphere", ""))
|
||||
assert default is not None, "Omnisphere default preset must exist"
|
||||
result = PresetTransformer.derive("Omnisphere", default, "pad")
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == len(default)
|
||||
|
||||
def test_derive_unknown_plugin_returns_default(self):
|
||||
"""derive() for unsupported plugin returns original chunks."""
|
||||
chunks = ["mock_chunk_1", "mock_chunk_2"]
|
||||
result = PresetTransformer.derive("NonexistentPlugin", chunks, "lead")
|
||||
assert result == chunks
|
||||
|
||||
def test_derive_preserves_chunk_count(self):
|
||||
"""derive() output has same number of chunks as input."""
|
||||
for plugin in ["Serum_2", "Decapitator", "Omnisphere"]:
|
||||
default = PLUGIN_PRESETS.get((plugin, ""))
|
||||
if not default:
|
||||
continue
|
||||
for role in ["bass", "lead", "drums", "pad"]:
|
||||
result = PresetTransformer.derive(plugin, default, role)
|
||||
assert len(result) == len(default), (
|
||||
f"{plugin}/{role} chunk count mismatch: "
|
||||
f"got {len(result)}, expected {len(default)}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Role-aware preset structure tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestRoleAwarePresets:
|
||||
"""PLUGIN_PRESETS is structured as {(plugin, role): chunks}."""
|
||||
|
||||
def test_default_role_entries_exist(self):
|
||||
"""All known plugins have a "" (default) role entry."""
|
||||
flat_keys = set()
|
||||
for (name, role), _ in PLUGIN_PRESETS.items():
|
||||
if role == "":
|
||||
flat_keys.add(name)
|
||||
# At minimum, the multi-role targets must have defaults
|
||||
for name in ["Serum_2", "Decapitator", "Omnisphere"]:
|
||||
assert name in flat_keys, f"{name} must have default role entry"
|
||||
|
||||
def test_role_specific_entries_exist(self):
|
||||
"""Multi-role plugins have their role-specific entries."""
|
||||
roles = {
|
||||
"Serum_2": ["bass", "lead"],
|
||||
"Decapitator": ["drumloop", "bass", "clap", "perc"],
|
||||
"Omnisphere": ["chords", "pad"],
|
||||
}
|
||||
for plugin, expected_roles in roles.items():
|
||||
for role in expected_roles:
|
||||
assert (plugin, role) in PLUGIN_PRESETS, (
|
||||
f"Missing ({plugin}, {role}) in PLUGIN_PRESETS"
|
||||
)
|
||||
|
||||
def test_role_entries_non_empty(self):
|
||||
"""Role-specific entries contain non-empty preset data."""
|
||||
for (name, role), chunks in PLUGIN_PRESETS.items():
|
||||
if role != "" and name in ("Serum_2", "Decapitator", "Omnisphere"):
|
||||
assert len(chunks) > 0, (
|
||||
f"({name}, {role}) has empty preset data"
|
||||
)
|
||||
|
||||
def test_unknown_role_falls_back_to_default(self):
|
||||
"""Role not present in PLUGIN_PRESETS → fall back to default."""
|
||||
# Simulate: a plugin has only default entry, no "pad" role
|
||||
# The lookup should return the "" entry
|
||||
from src.reaper_builder import _resolve_preset
|
||||
|
||||
# Gullfoss_Master doesn't have multi-role entries, only ""
|
||||
result = _resolve_preset("Gullfoss_Master", "pad")
|
||||
default = PLUGIN_PRESETS.get(("Gullfoss_Master", ""))
|
||||
assert result == default, (
|
||||
"Unknown role should fall back to default preset"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Integration: make_plugin + role threading
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMakePluginRoleThreading:
|
||||
"""make_plugin with role correctly sets PluginDef.role and preset_data."""
|
||||
|
||||
def test_make_plugin_with_role_sets_role_field(self):
|
||||
"""make_plugin(key, idx, role=...) sets PluginDef.role."""
|
||||
from scripts.compose import make_plugin
|
||||
|
||||
p = make_plugin("Serum_2", 0, role="bass")
|
||||
assert p.role == "bass"
|
||||
assert p.preset_data is not None
|
||||
|
||||
def test_make_plugin_without_role_defaults_to_empty(self):
|
||||
"""make_plugin(key, idx) without role sets role="" (backward compat)."""
|
||||
from scripts.compose import make_plugin
|
||||
|
||||
p = make_plugin("Decapitator", 0)
|
||||
assert p.role == ""
|
||||
|
||||
def test_make_plugin_different_roles_lookup_correct_entries(self):
|
||||
"""Different roles resolve to their respective PLUGIN_PRESETS entries."""
|
||||
from scripts.compose import make_plugin
|
||||
|
||||
# Both should return data — in MVP they're identical but structure is correct
|
||||
p_bass = make_plugin("Serum_2", 0, role="bass")
|
||||
p_lead = make_plugin("Serum_2", 0, role="lead")
|
||||
|
||||
# Both should have non-None preset data
|
||||
assert p_bass.preset_data is not None, "bass role should have preset_data"
|
||||
assert p_lead.preset_data is not None, "lead role should have preset_data"
|
||||
|
||||
# Both should be lists with content
|
||||
assert isinstance(p_bass.preset_data, list)
|
||||
assert isinstance(p_lead.preset_data, list)
|
||||
assert len(p_bass.preset_data) > 0
|
||||
assert len(p_lead.preset_data) > 0
|
||||
|
||||
def test_make_plugin_unknown_role_falls_back(self):
|
||||
"""Unknown role returns preset from "" entry (if available)."""
|
||||
from scripts.compose import make_plugin
|
||||
|
||||
# "pad" role is not valid for Serum_2 (Omnisphere handles pad)
|
||||
p = make_plugin("Serum_2", 0, role="pad")
|
||||
# Should still get the default Serum_2 preset
|
||||
assert p.preset_data is not None
|
||||
assert p.role == "pad"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backward compatibility
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBackwardCompatibility:
|
||||
"""Existing behavior preserved with no role."""
|
||||
|
||||
def test_make_plugin_known_key_still_works(self):
|
||||
"""make_plugin with known registry key (no role) works as before."""
|
||||
from scripts.compose import make_plugin
|
||||
p = make_plugin("Decapitator", 0)
|
||||
assert p.name == "Decapitator"
|
||||
assert p.index == 0
|
||||
assert p.role == ""
|
||||
|
||||
def test_make_plugin_unknown_key_still_works(self):
|
||||
"""make_plugin with unknown key (no role) returns PluginDef."""
|
||||
from scripts.compose import make_plugin
|
||||
p = make_plugin("NonExistent", 2)
|
||||
assert p.name == "NonExistent"
|
||||
assert p.index == 2
|
||||
assert p.role == ""
|
||||
Reference in New Issue
Block a user