- Add VST3_PRESETS dict with base64 preset data for all 10 plugins (required by REAPER to load VST3) - Fix VST3 registry: correct display names, filenames, and uniqueid GUIDs - Add ~50 lines of REAPER project metadata (PANLAW, SAMPLERATE, METRONOME, etc.) - Add 25 track attributes (PEAKCOL, BEAT, AUTOMODE, etc.) and FX chain metadata - Remove unrecognized tokens (RENDER_CFG, PROJBAY, WAK) that caused REAPER warnings - Update compose.py with section-based arrangement and registry key names - Add SectionDef to schema - 72 tests passing
209 lines
7.2 KiB
Python
209 lines
7.2 KiB
Python
"""Tests for section builder — SectionDef, build_fx_chain, effect alias mapping."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parents[1]))
|
|
|
|
import pytest
|
|
from src.core.schema import SectionDef, PluginDef
|
|
|
|
|
|
class TestSectionDef:
|
|
"""Test SectionDef dataclass."""
|
|
|
|
def test_section_def_instantiation(self):
|
|
"""SectionDef creates with name, bars, energy."""
|
|
section = SectionDef(name="chorus", bars=8, energy=0.9)
|
|
assert section.name == "chorus"
|
|
assert section.bars == 8
|
|
assert section.energy == 0.9
|
|
# velocity_mult and vol_mult default to 1.0 (not derived from energy)
|
|
assert section.velocity_mult == 1.0
|
|
assert section.vol_mult == 1.0
|
|
|
|
def test_section_def_default_energy(self):
|
|
"""SectionDef defaults energy to 0.5, velocity_mult/vol_mult to 1.0."""
|
|
section = SectionDef(name="verse", bars=8)
|
|
assert section.energy == 0.5
|
|
assert section.velocity_mult == 1.0
|
|
assert section.vol_mult == 1.0
|
|
|
|
def test_section_def_custom_mults(self):
|
|
"""SectionDef accepts custom velocity_mult and vol_mult via __init__ args."""
|
|
section = SectionDef(
|
|
name="intro", bars=4, energy=0.3,
|
|
velocity_mult=0.4, vol_mult=0.6
|
|
)
|
|
assert section.velocity_mult == 0.4
|
|
assert section.vol_mult == 0.6
|
|
|
|
|
|
class TestVST3Effects:
|
|
"""Test VST3 premium plugin mappings."""
|
|
|
|
def test_vst3_effects_defined(self):
|
|
"""_VST3_EFFECTS maps effect names to VST3 plugins."""
|
|
from scripts.compose import _VST3_EFFECTS
|
|
assert "Pro-Q 3" in _VST3_EFFECTS
|
|
assert "Pro-C 2" in _VST3_EFFECTS
|
|
assert "Pro-R 2" in _VST3_EFFECTS
|
|
assert "Timeless 3" in _VST3_EFFECTS
|
|
|
|
def test_fruity_eq_maps_to_proq3(self):
|
|
"""Fruity Parametric EQ 2 → FabFilter Pro-Q 3 via normalization."""
|
|
from scripts.compose import _VST3_EFFECTS
|
|
# Fruity Parametric EQ 2 normalizes to Pro-Q 3
|
|
registry_key, filename = _VST3_EFFECTS["Pro-Q 3"]
|
|
assert registry_key == "FabFilter Pro-Q 3"
|
|
assert filename == "FabFilter Pro-Q 3.vst3"
|
|
|
|
def test_fruity_compressor_maps_to_proc2(self):
|
|
"""Fruity Compressor → FabFilter Pro-C 2 via normalization."""
|
|
from scripts.compose import _VST3_EFFECTS
|
|
registry_key, filename = _VST3_EFFECTS["Pro-C 2"]
|
|
assert registry_key == "FabFilter Pro-C 2"
|
|
assert filename == "FabFilter Pro-C 2.vst3"
|
|
|
|
def test_pro_r_maps_to_pror2(self):
|
|
"""Pro-R 2 → FabFilter Pro-R 2."""
|
|
from scripts.compose import _VST3_EFFECTS
|
|
registry_key, filename = _VST3_EFFECTS["Pro-R 2"]
|
|
assert registry_key == "FabFilter Pro-R 2"
|
|
assert filename == "FabFilter Pro-R 2.vst3"
|
|
|
|
def test_unknown_effect_returns_none(self):
|
|
"""Unknown effect names return no VST3 info."""
|
|
from scripts.compose import _VST3_EFFECTS
|
|
assert _VST3_EFFECTS.get("Some Unknown Plugin") is None
|
|
|
|
|
|
class TestBuildFxChain:
|
|
"""Test build_fx_chain function."""
|
|
|
|
def test_build_fx_chain_drums(self):
|
|
"""build_fx_chain returns PluginDef list for drums role."""
|
|
from scripts.compose import build_fx_chain
|
|
|
|
genre_config = {
|
|
"mix": {
|
|
"per_role": {
|
|
"drums": {
|
|
"effects": ["Fruity Parametric EQ 2", "Fruity Compressor"],
|
|
}
|
|
}
|
|
}
|
|
}
|
|
plugins = build_fx_chain("drums", genre_config, [])
|
|
assert len(plugins) == 2
|
|
# Fruity Parametric EQ 2 → Pro-Q 3
|
|
assert "FabFilter" in plugins[0].name
|
|
assert ".vst3" in plugins[0].path
|
|
# Fruity Compressor → Pro-C 2
|
|
assert "FabFilter" in plugins[1].name
|
|
|
|
def test_build_fx_chain_bass(self):
|
|
"""build_fx_chain returns PluginDef list for bass role."""
|
|
from scripts.compose import build_fx_chain
|
|
|
|
genre_config = {
|
|
"mix": {
|
|
"per_role": {
|
|
"bass": {
|
|
"effects": ["Fruity Parametric EQ 2", "Saturn 2"],
|
|
}
|
|
}
|
|
}
|
|
}
|
|
plugins = build_fx_chain("bass", genre_config, [])
|
|
assert len(plugins) == 2
|
|
# Saturn 2 → FabFilter Saturn 2
|
|
assert "Saturn" in plugins[1].name
|
|
|
|
def test_build_fx_chain_empty_effects(self):
|
|
"""build_fx_chain returns empty list when no effects configured."""
|
|
from scripts.compose import build_fx_chain
|
|
|
|
genre_config = {"mix": {"per_role": {}}}
|
|
plugins = build_fx_chain("drums", genre_config, [])
|
|
assert plugins == []
|
|
|
|
def test_build_fx_chain_unknown_effect_uses_name(self):
|
|
"""Unknown effect names are used as-is."""
|
|
from scripts.compose import build_fx_chain
|
|
|
|
genre_config = {
|
|
"mix": {
|
|
"per_role": {
|
|
"lead": {
|
|
"effects": ["Some Unknown FX"],
|
|
}
|
|
}
|
|
}
|
|
}
|
|
plugins = build_fx_chain("lead", genre_config, [])
|
|
# Unknown effects are skipped (not added to plugins)
|
|
assert len(plugins) == 0
|
|
|
|
|
|
class TestInstrumentPlugins:
|
|
"""Test instrument plugin helpers (Serum 2, Omnisphere)."""
|
|
|
|
def test_serum2_plugin_def(self):
|
|
"""serum2() returns PluginDef with registry key name."""
|
|
from scripts.compose import serum2
|
|
|
|
plugin = serum2()
|
|
assert plugin.name == "Serum2"
|
|
assert plugin.path == "Serum2.vst3"
|
|
assert plugin.index == 0
|
|
|
|
def test_omnisphere_plugin_def(self):
|
|
"""omnisphere() returns PluginDef with registry key name."""
|
|
from scripts.compose import omnisphere
|
|
|
|
plugin = omnisphere()
|
|
assert plugin.name == "Omnisphere"
|
|
assert plugin.path == "Omnisphere.vst3"
|
|
assert plugin.index == 0
|
|
|
|
|
|
class TestCreateReturnTracks:
|
|
"""Test create_return_tracks function."""
|
|
|
|
def test_create_return_tracks_returns_two(self):
|
|
"""create_return_tracks returns [Reverb, Delay] tracks."""
|
|
from scripts.compose import create_return_tracks
|
|
|
|
tracks = create_return_tracks()
|
|
assert len(tracks) == 2
|
|
assert tracks[0].name == "Reverb"
|
|
assert tracks[1].name == "Delay"
|
|
|
|
def test_reverb_track_has_pro_r2(self):
|
|
"""Reverb return track has FabFilter Pro-R 2 plugin."""
|
|
from scripts.compose import create_return_tracks
|
|
|
|
tracks = create_return_tracks()
|
|
reverb = tracks[0]
|
|
assert len(reverb.plugins) == 1
|
|
assert "FabFilter" in reverb.plugins[0].name
|
|
assert ".vst3" in reverb.plugins[0].path
|
|
|
|
def test_delay_track_has_timeless3(self):
|
|
"""Delay return track has FabFilter Timeless 3 plugin."""
|
|
from scripts.compose import create_return_tracks
|
|
|
|
tracks = create_return_tracks()
|
|
delay = tracks[1]
|
|
assert len(delay.plugins) == 1
|
|
assert "Timeless" in delay.plugins[0].name
|
|
assert ".vst3" in delay.plugins[0].path
|
|
|
|
def test_return_tracks_have_volume_0_7(self):
|
|
"""Return tracks have volume 0.7."""
|
|
from scripts.compose import create_return_tracks
|
|
|
|
tracks = create_return_tracks()
|
|
for t in tracks:
|
|
assert t.volume == 0.7 |