"""Tests for section builder — SectionDef, track builders, plugin helpers.""" 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: def test_section_def_instantiation(self): section = SectionDef(name="chorus", bars=8, energy=0.9) assert section.name == "chorus" assert section.bars == 8 assert section.energy == 0.9 assert section.velocity_mult == 1.0 assert section.vol_mult == 1.0 def test_section_def_default_energy(self): 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): 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 TestBuildSectionStructureMultipliers: """Verify build_section_structure() populates velocity_mult and vol_mult.""" @staticmethod def _get_sections(): from scripts.compose import build_section_structure sections, _offsets = build_section_structure() return {s.name: s for s in sections} def test_intro_has_low_multipliers(self): sections = self._get_sections() assert sections["intro"].velocity_mult == 0.6 assert sections["intro"].vol_mult == 0.70 def test_verse_has_mid_multipliers(self): sections = self._get_sections() assert sections["verse"].velocity_mult == 0.7 assert sections["verse"].vol_mult == 0.85 def test_pre_chorus_has_high_multipliers(self): sections = self._get_sections() assert sections["pre-chorus"].velocity_mult == 0.85 assert sections["pre-chorus"].vol_mult == 0.95 def test_chorus_has_full_multipliers(self): sections = self._get_sections() assert sections["chorus"].velocity_mult == 1.0 assert sections["chorus"].vol_mult == 1.0 def test_verse2_same_as_verse(self): sections = self._get_sections() assert sections["verse2"].velocity_mult == 0.7 assert sections["verse2"].vol_mult == 0.85 def test_chorus2_same_as_chorus(self): sections = self._get_sections() assert sections["chorus2"].velocity_mult == 1.0 assert sections["chorus2"].vol_mult == 1.0 def test_bridge_has_low_multipliers(self): sections = self._get_sections() assert sections["bridge"].velocity_mult == 0.6 assert sections["bridge"].vol_mult == 0.75 def test_final_has_full_multipliers(self): sections = self._get_sections() assert sections["final"].velocity_mult == 1.0 assert sections["final"].vol_mult == 1.0 def test_outro_has_lowest_multipliers(self): sections = self._get_sections() assert sections["outro"].velocity_mult == 0.4 assert sections["outro"].vol_mult == 0.60 def test_all_sections_have_multipliers(self): """Every section name in SECTIONS has a corresponding entry in multipliers.""" sections = self._get_sections() from scripts.compose import SECTIONS expected_names = {name for name, _, _, _ in SECTIONS} assert set(sections.keys()) == expected_names class TestSectionActiveHelper: """Tests for _section_active() centralized activity helper.""" @staticmethod def _get_activity(): from scripts.compose import TRACK_ACTIVITY return TRACK_ACTIVITY def test_intro_drumloop_active(self): from scripts.compose import _section_active assert _section_active("intro", "drumloop", self._get_activity()) is True def test_intro_bass_inactive(self): from scripts.compose import _section_active assert _section_active("intro", "bass", self._get_activity()) is False def test_intro_chords_inactive(self): from scripts.compose import _section_active assert _section_active("intro", "chords", self._get_activity()) is False def test_intro_lead_inactive(self): from scripts.compose import _section_active assert _section_active("intro", "lead", self._get_activity()) is False def test_chorus_all_active(self): from scripts.compose import _section_active activity = self._get_activity() for role in ("drumloop", "perc", "bass", "chords", "lead", "clap", "pad"): assert _section_active("chorus", role, activity) is True, f"chorus.{role} should be active" def test_verse_only_drumloop_bass_chords_active(self): from scripts.compose import _section_active activity = self._get_activity() assert _section_active("verse", "drumloop", activity) is True assert _section_active("verse", "bass", activity) is True assert _section_active("verse", "chords", activity) is True assert _section_active("verse", "lead", activity) is False assert _section_active("verse", "perc", activity) is False assert _section_active("verse", "clap", activity) is False def test_pre_chorus_section_name(self): """The section is named pre-chorus, not build.""" from scripts.compose import _section_active activity = self._get_activity() assert "pre-chorus" in activity, "pre-chorus must be a key in TRACK_ACTIVITY" assert "build" not in activity, "build must NOT be a key in TRACK_ACTIVITY" assert _section_active("pre-chorus", "bass", activity) is True def test_unknown_section_returns_false(self): from scripts.compose import _section_active assert _section_active("xyz", "bass", self._get_activity()) is False def test_unknown_role_returns_false(self): from scripts.compose import _section_active assert _section_active("chorus", "banjo", self._get_activity()) is False def test_outro_has_drumloop_and_pad(self): from scripts.compose import _section_active activity = self._get_activity() assert _section_active("outro", "drumloop", activity) is True assert _section_active("outro", "pad", activity) is True assert _section_active("outro", "bass", activity) is False def test_bridge_has_drumloop_pad_lead(self): from scripts.compose import _section_active activity = self._get_activity() assert _section_active("bridge", "drumloop", activity) is True assert _section_active("bridge", "pad", activity) is True assert _section_active("bridge", "lead", activity) is True assert _section_active("bridge", "bass", activity) is False assert _section_active("bridge", "chords", activity) is False def test_final_has_perc_bass_chords_lead_clap_pad(self): from scripts.compose import _section_active activity = self._get_activity() assert _section_active("final", "drumloop", activity) is True assert _section_active("final", "perc", activity) is True assert _section_active("final", "bass", activity) is True assert _section_active("final", "chords", activity) is True assert _section_active("final", "lead", activity) is True assert _section_active("final", "clap", activity) is True assert _section_active("final", "pad", activity) is True class TestPluginRegistry: def test_plugins_in_registry(self): from src.reaper_builder import PLUGIN_REGISTRY assert "Decapitator" in PLUGIN_REGISTRY assert "EchoBoy" in PLUGIN_REGISTRY assert "Serum_2" in PLUGIN_REGISTRY assert "Omnisphere" in PLUGIN_REGISTRY assert "Pro-Q_3" in PLUGIN_REGISTRY assert "Pro-C_2" in PLUGIN_REGISTRY assert "Pro-L_2" in PLUGIN_REGISTRY assert "FabFilter_Pro-R_2" in PLUGIN_REGISTRY assert "ValhallaDelay" in PLUGIN_REGISTRY assert "PhaseMistress" in PLUGIN_REGISTRY assert "Tremolator" in PLUGIN_REGISTRY assert "Radiator" in PLUGIN_REGISTRY assert "Gullfoss_Master" in PLUGIN_REGISTRY assert "VC_76" in PLUGIN_REGISTRY class TestMakePlugin: def test_make_plugin_known_key(self): from scripts.compose import make_plugin p = make_plugin("Decapitator", 0) assert p.name == "Decapitator" assert p.index == 0 def test_make_plugin_unknown_key(self): from scripts.compose import make_plugin p = make_plugin("NonExistent", 2) assert p.name == "NonExistent" assert p.index == 2 class TestBuildFxChain: def test_build_fx_chain_returns_list(self): from scripts.compose import build_fx_chain assert build_fx_chain() == [] def test_build_fx_chain_with_args(self): from scripts.compose import build_fx_chain assert build_fx_chain("drums", {}, []) == [] class TestCreateReturnTracks: def test_create_return_tracks_returns_two(self): 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): from scripts.compose import create_return_tracks tracks = create_return_tracks() reverb = tracks[0] assert len(reverb.plugins) == 1 assert "Pro-R" in reverb.plugins[0].name def test_delay_track_has_valhalla(self): from scripts.compose import create_return_tracks tracks = create_return_tracks() delay = tracks[1] assert len(delay.plugins) == 1 assert "Valhalla" in delay.plugins[0].name def test_return_tracks_have_volume_0_7(self): from scripts.compose import create_return_tracks tracks = create_return_tracks() for t in tracks: assert t.volume == 0.7 class TestMusicTheory: def test_parse_key_minor(self): from scripts.compose import parse_key root, minor = parse_key("Am") assert root == "A" assert minor is True def test_parse_key_major(self): from scripts.compose import parse_key root, minor = parse_key("C") assert root == "C" assert minor is False def test_root_to_midi(self): from scripts.compose import NOTE_TO_MIDI assert NOTE_TO_MIDI["A"] + (4 + 1) * 12 == 69 assert NOTE_TO_MIDI["C"] + (4 + 1) * 12 == 60 def test_build_chord_major(self): from scripts.compose import build_chord chord = build_chord(60, "major") assert chord == [60, 64, 67] def test_build_chord_minor(self): from scripts.compose import build_chord chord = build_chord(60, "minor") assert chord == [60, 63, 67] def test_pentatonic_minor(self): from scripts.compose import get_pentatonic notes = get_pentatonic("A", True, 4) assert notes[0] == 69 # A4 assert len(notes) == 5 def test_pentatonic_major(self): from scripts.compose import get_pentatonic notes = get_pentatonic("C", False, 4) assert notes[0] == 60 # C4 assert len(notes) == 5