FASE 3 - Human Feel & Dynamics (10/11 tasks): - apply_clip_fades() - T041: Fade automation per section - write_volume_automation() - T042: Curves (linear, exp, s_curve, punch) - apply_sidechain_pump() - T045: Sidechain by intensity/style - inject_pattern_fills() - T048: Snare rolls, fills by density - humanize_set() - T050: Timing + velocity + groove automation FASE 4 - Key Compatibility & Tonal (9/12 tasks): - audio_key_compatibility.py: Full KEY_COMPATIBILITY_MATRIX - analyze_key_compatibility() - T053: Harmonic compatibility scoring - suggest_key_change() - T054: Circle of fifths modulation - validate_sample_key() - T055: Sample key validation - analyze_spectral_fit() - T057/T062: Spectral role matching FASE 6 - Mastering & QA (8/13 tasks): - calibrate_gain_staging() - T079: Auto gain by bus targets - run_mix_quality_check() - T085: LUFS, peaks, L/R balance - export_stem_mixdown() - T087: 24-bit/44.1kHz stem export New files: - audio_key_compatibility.py (T052) - bus_routing_fix.py (T101-T104) - validation_system_fix.py (T105-T106) Total: 76/110 tasks (69%), 71 MCP tools exposed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
"""
|
|
test_sample_selector.py - Tests para SampleSelector
|
|
T101-T103: Unit tests
|
|
"""
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import unittest
|
|
from unittest.mock import Mock, MagicMock
|
|
from sample_selector import SampleSelector, Sample
|
|
|
|
|
|
class TestSampleSelector(unittest.TestCase):
|
|
"""Tests para SampleSelector"""
|
|
|
|
def setUp(self):
|
|
self.selector = SampleSelector()
|
|
|
|
def test_palette_bonus_exact_match(self):
|
|
"""T026: Bonus 1.4x para folder ancla exacto."""
|
|
# Simular que tenemos un palette
|
|
self.selector.set_palette_data({'drums': '/samples/Kicks'})
|
|
|
|
# Sample en folder exacto
|
|
bonus = self.selector._calculate_palette_bonus('/samples/Kicks/kick_01.wav', '/samples/Kicks')
|
|
self.assertEqual(bonus, 1.4)
|
|
|
|
def test_palette_bonus_sibling_folder(self):
|
|
"""T026: Bonus 1.2x para folder hermano."""
|
|
self.selector.set_palette_data({'drums': '/samples/Kicks'})
|
|
|
|
# Sample en folder hermano
|
|
bonus = self.selector._calculate_palette_bonus('/samples/Snares/snare_01.wav', '/samples/Kicks')
|
|
self.assertEqual(bonus, 1.2)
|
|
|
|
|
|
def test_palette_bonus_different_folder(self):
|
|
"""T026: Penalizacion 0.9x para folder completamente diferente."""
|
|
self.selector.set_palette_data({'drums': '/Library/Kicks'})
|
|
|
|
# Sample en folder completamente diferente (no es hermano)
|
|
bonus = self.selector._calculate_palette_bonus('/OtherLibrary/Pads/pad.wav', '/Library/Kicks')
|
|
self.assertEqual(bonus, 0.9)
|
|
|
|
def test_role_to_bus_mapping(self):
|
|
"""Test mapeo de roles a buses."""
|
|
self.assertEqual(self.selector._role_to_bus('kick'), 'drums')
|
|
self.assertEqual(self.selector._role_to_bus('bass'), 'bass')
|
|
self.assertEqual(self.selector._role_to_bus('synth'), 'music')
|
|
|
|
def test_fatigue_calculation(self):
|
|
"""T022: Cálculo correcto de fatiga."""
|
|
fatigue_data = {
|
|
'/samples/kick_01.wav': {'kick': {'uses': 5}}
|
|
}
|
|
self.selector.set_fatigue_data(fatigue_data)
|
|
|
|
# 5 usos = fatiga moderada = 0.50
|
|
factor = self.selector._get_persistent_fatigue('/samples/kick_01.wav', 'kick')
|
|
self.assertEqual(factor, 0.50)
|
|
|
|
|
|
class TestSampleValidation(unittest.TestCase):
|
|
"""Tests para validación de samples"""
|
|
|
|
def test_sample_type_detection(self):
|
|
"""Test detección de tipo de sample."""
|
|
from audio_analyzer import AudioAnalyzer
|
|
|
|
analyzer = AudioAnalyzer(backend="basic")
|
|
sample_type = analyzer._classify_by_name("Kick_120_BPM.wav")
|
|
self.assertIn(sample_type.value.lower(), ['kick', 'unknown'])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|