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:
renato97
2026-05-03 23:54:29 -03:00
parent 48bc271afc
commit 014e636889
51 changed files with 11394 additions and 113 deletions

View File

@@ -1,12 +1,60 @@
"""Tests for src/core/schema.py — SongDefinition, TrackDef, ClipDef, MidiNote."""
"""Tests for src/core/schema.py — SongDefinition, TrackDef, ClipDef, MidiNote, CCEvent."""
import dataclasses
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parents[1]))
import pytest
from src.core.schema import SongDefinition, SongMeta, TrackDef, ClipDef, MidiNote
from src.core.schema import SongDefinition, SongMeta, TrackDef, ClipDef, MidiNote, CCEvent
class TestCCEvent:
"""Test CCEvent dataclass (Phase 1: schema)."""
def test_ccevent_round_trip(self):
"""CCEvent round-trips correctly through dataclass construction."""
evt = CCEvent(controller=11, time=0.5, value=50)
assert evt.controller == 11
assert evt.time == 0.5
assert evt.value == 50
def test_ccevent_defaults(self):
"""CCEvent has no default fields — all are required."""
from dataclasses import fields as dc_fields
field_names = [f.name for f in dc_fields(CCEvent)]
no_default = []
for f in dc_fields(CCEvent):
if f.default is f.default_factory is dataclasses.MISSING:
no_default.append(f.name)
assert "controller" in no_default
assert "time" in no_default
assert "value" in no_default
def test_clipdef_with_midi_cc(self):
"""ClipDef with midi_cc field."""
cc = [CCEvent(11, 0.0, 50), CCEvent(11, 0.18, 127)]
clip = ClipDef(position=0.0, length=16.0, name="Test", midi_cc=cc)
assert len(clip.midi_cc) == 2
assert clip.midi_cc[0].controller == 11
assert clip.midi_cc[0].time == 0.0
assert clip.midi_cc[0].value == 50
def test_clipdef_midi_cc_default_is_empty(self):
"""ClipDef.midi_cc defaults to empty list."""
clip = ClipDef(position=0.0, length=16.0, name="Test")
assert clip.midi_cc == []
def test_song_validate_empty_midi_cc(self):
"""Song.validate() passes with empty midi_cc (no regression)."""
meta = SongMeta(bpm=95, key="Am")
note = MidiNote(pitch=60, start=0.0, duration=1.0, velocity=100)
clip = ClipDef(position=0.0, length=16.0, name="Test", midi_notes=[note], midi_cc=[])
track = TrackDef(name="Test", clips=[clip])
song = SongDefinition(meta=meta, tracks=[track])
errors = song.validate()
assert errors == []
class TestSongDefinitionInstantiation:
@@ -135,3 +183,30 @@ class TestMidiNote:
"""MidiNote does NOT clamp — accepts any int (caller's responsibility)."""
note = MidiNote(pitch=60, start=0.0, duration=1.0, velocity=200)
assert note.velocity == 200
class TestClipDefVolMult:
"""Test ClipDef.vol_mult default and behavior."""
def test_vol_mult_default_is_one(self):
"""ClipDef.vol_mult defaults to 1.0."""
clip = ClipDef(position=0.0, length=16.0, name="Test")
assert clip.vol_mult == 1.0
def test_vol_mult_custom_value(self):
"""ClipDef.vol_mult accepts custom value."""
clip = ClipDef(position=0.0, length=16.0, name="Test", vol_mult=0.7)
assert clip.vol_mult == 0.7
def test_audio_clip_vol_mult_default_is_one(self):
"""Audio clip with default vol_mult=1.0 has no D_VOL side effect."""
clip = ClipDef(position=0.0, length=16.0, audio_path="test.wav", vol_mult=1.0)
assert clip.is_audio
assert clip.vol_mult == 1.0
def test_midi_clip_vol_mult_default_is_one(self):
"""MIDI clip with default vol_mult=1.0 has no velocity scaling."""
note = MidiNote(pitch=60, start=0.0, duration=1.0, velocity=100)
clip = ClipDef(position=0.0, length=16.0, midi_notes=[note], vol_mult=1.0)
assert clip.is_midi
assert clip.vol_mult == 1.0