Refine melodic threshold to continuous scoring

- Use continuous scoring instead of binary pass/fail
- Below min: score = (cents / min)^2
- Above max: score = ((1200 - cents) / (1200 - max))^2
- Use product across voices to penalize worst offenders
This commit is contained in:
Michael Winter 2026-03-16 14:11:27 +01:00
parent 1926930c3d
commit 1eb95ffad7

View file

@ -274,23 +274,36 @@ class PathFinder:
} }
def _factor_melodic_threshold(self, edge_data: dict, config: dict) -> float: def _factor_melodic_threshold(self, edge_data: dict, config: dict) -> float:
"""Returns 1.0 if all voice movements are within melodic threshold, 0.0 otherwise.""" """Returns continuous score based on melodic threshold.
# Check weight - if 0, return 1.0 (neutral)
if config.get("weight_melodic", 1) == 0:
return 1.0
- cents == 0: score = 1.0 (no movement is always ideal)
- Below min (0 < cents < min): score = (cents / min)^2
- Within range (min <= cents <= max): score = 1.0
- Above max (cents > max): score = ((1200 - cents) / (1200 - max))^2
Returns product of all voice scores.
"""
melodic_min = config.get("melodic_threshold_min", 0) melodic_min = config.get("melodic_threshold_min", 0)
melodic_max = config.get("melodic_threshold_max", float("inf")) melodic_max = config.get("melodic_threshold_max", float("inf"))
cent_diffs = edge_data.get("cent_diffs", []) cent_diffs = edge_data.get("cent_diffs", [])
if melodic_min is not None or melodic_max is not None: if not cent_diffs:
for cents in cent_diffs: return 1.0
if melodic_min is not None and cents < melodic_min:
return 0.0 product = 1.0
if melodic_max is not None and cents > melodic_max: for cents in cent_diffs:
return 0.0 if cents == 0:
return 1.0 score = 1.0
elif cents < melodic_min:
score = (cents / melodic_min) ** 2
elif cents > melodic_max:
score = ((1200 - cents) / (1200 - melodic_max)) ** 2
else:
score = 1.0
product *= score
return product
def _factor_direct_tuning(self, edge_data: dict, config: dict) -> float: def _factor_direct_tuning(self, edge_data: dict, config: dict) -> float:
"""Returns 1.0 if directly tunable (or disabled), 0.0 otherwise.""" """Returns 1.0 if directly tunable (or disabled), 0.0 otherwise."""