Add harmonic compactness factor
- Compute sum of harmonic distances between all pitch pairs - Lower sum = more compact chord = higher probability - Uses pitch_difference() to get diff, then log2(n*d) for distance - Add CLI arg: --weight-harmonic-compactness
This commit is contained in:
parent
65e3a64a0c
commit
62e6a75f4f
|
|
@ -435,6 +435,12 @@ def main():
|
||||||
default=5,
|
default=5,
|
||||||
help="Maximum consecutive selections before blocking (default: 5)",
|
help="Maximum consecutive selections before blocking (default: 5)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--weight-harmonic-compactness",
|
||||||
|
type=float,
|
||||||
|
default=0,
|
||||||
|
help="Weight for harmonic compactness factor - favors compact chords (0=disabled, default: 0)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--weight-target-register",
|
"--weight-target-register",
|
||||||
type=float,
|
type=float,
|
||||||
|
|
@ -673,6 +679,7 @@ def main():
|
||||||
weights_config["weight_dca_voice_movement"] = args.weight_dca_voice_movement
|
weights_config["weight_dca_voice_movement"] = args.weight_dca_voice_movement
|
||||||
weights_config["weight_rgr_voice_movement"] = args.weight_rgr_voice_movement
|
weights_config["weight_rgr_voice_movement"] = args.weight_rgr_voice_movement
|
||||||
weights_config["rgr_voice_movement_threshold"] = args.rgr_voice_movement_threshold
|
weights_config["rgr_voice_movement_threshold"] = args.rgr_voice_movement_threshold
|
||||||
|
weights_config["weight_harmonic_compactness"] = args.weight_harmonic_compactness
|
||||||
|
|
||||||
# Target register
|
# Target register
|
||||||
if args.target_register != 0:
|
if args.target_register != 0:
|
||||||
|
|
|
||||||
40
src/path.py
40
src/path.py
|
|
@ -142,6 +142,10 @@ class Path:
|
||||||
change_before,
|
change_before,
|
||||||
self.weights_config,
|
self.weights_config,
|
||||||
),
|
),
|
||||||
|
"harmonic_compactness": self._factor_harmonic_compactness(
|
||||||
|
destination_chord,
|
||||||
|
self.weights_config,
|
||||||
|
),
|
||||||
"target_register": self._factor_target_register(
|
"target_register": self._factor_target_register(
|
||||||
path_chords,
|
path_chords,
|
||||||
destination_chord,
|
destination_chord,
|
||||||
|
|
@ -207,6 +211,7 @@ class Path:
|
||||||
hamiltonian_values = [c.scores.get("dca_hamiltonian", 0) for c in candidates]
|
hamiltonian_values = [c.scores.get("dca_hamiltonian", 0) for c in candidates]
|
||||||
dca_values = [c.scores.get("dca_voice_movement", 0) for c in candidates]
|
dca_values = [c.scores.get("dca_voice_movement", 0) for c in candidates]
|
||||||
rgr_values = [c.scores.get("rgr_voice_movement", 0) for c in candidates]
|
rgr_values = [c.scores.get("rgr_voice_movement", 0) for c in candidates]
|
||||||
|
compact_values = [c.scores.get("harmonic_compactness", 0) for c in candidates]
|
||||||
target_values = [c.scores.get("target_register", 0) for c in candidates]
|
target_values = [c.scores.get("target_register", 0) for c in candidates]
|
||||||
|
|
||||||
def sum_normalize(values: list) -> list | None:
|
def sum_normalize(values: list) -> list | None:
|
||||||
|
|
@ -221,6 +226,7 @@ class Path:
|
||||||
hamiltonian_norm = sum_normalize(hamiltonian_values)
|
hamiltonian_norm = sum_normalize(hamiltonian_values)
|
||||||
dca_norm = sum_normalize(dca_values)
|
dca_norm = sum_normalize(dca_values)
|
||||||
rgr_norm = sum_normalize(rgr_values)
|
rgr_norm = sum_normalize(rgr_values)
|
||||||
|
compact_norm = sum_normalize(compact_values)
|
||||||
target_norm = sum_normalize(target_values)
|
target_norm = sum_normalize(target_values)
|
||||||
|
|
||||||
weights = []
|
weights = []
|
||||||
|
|
@ -250,6 +256,8 @@ class Path:
|
||||||
w *= dca_norm[i] * config.get("weight_dca_voice_movement", 1)
|
w *= dca_norm[i] * config.get("weight_dca_voice_movement", 1)
|
||||||
if rgr_norm:
|
if rgr_norm:
|
||||||
w *= rgr_norm[i] * config.get("weight_rgr_voice_movement", 1)
|
w *= rgr_norm[i] * config.get("weight_rgr_voice_movement", 1)
|
||||||
|
if compact_norm:
|
||||||
|
w *= compact_norm[i] * config.get("weight_harmonic_compactness", 1)
|
||||||
if target_norm:
|
if target_norm:
|
||||||
w *= target_norm[i] * config.get("weight_target_register", 1)
|
w *= target_norm[i] * config.get("weight_target_register", 1)
|
||||||
|
|
||||||
|
|
@ -262,6 +270,7 @@ class Path:
|
||||||
"dca_hamiltonian": hamiltonian_norm[i] if hamiltonian_norm else None,
|
"dca_hamiltonian": hamiltonian_norm[i] if hamiltonian_norm else None,
|
||||||
"dca_voice_movement": dca_norm[i] if dca_norm else None,
|
"dca_voice_movement": dca_norm[i] if dca_norm else None,
|
||||||
"rgr_voice_movement": rgr_norm[i] if rgr_norm else None,
|
"rgr_voice_movement": rgr_norm[i] if rgr_norm else None,
|
||||||
|
"harmonic_compactness": compact_norm[i] if compact_norm else None,
|
||||||
"target_register": target_norm[i] if target_norm else None,
|
"target_register": target_norm[i] if target_norm else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,6 +422,32 @@ class Path:
|
||||||
|
|
||||||
return sum_repeating
|
return sum_repeating
|
||||||
|
|
||||||
|
def _factor_harmonic_compactness(
|
||||||
|
self,
|
||||||
|
destination_chord: Chord,
|
||||||
|
config: dict,
|
||||||
|
) -> float:
|
||||||
|
"""Returns probability based on sum of harmonic distances between all pairs.
|
||||||
|
|
||||||
|
Lower sum = more compact = higher probability.
|
||||||
|
"""
|
||||||
|
if config.get("weight_harmonic_compactness", 0) == 0:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
pitches = destination_chord.pitches
|
||||||
|
if len(pitches) < 2:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
total_distance = 0
|
||||||
|
for i in range(len(pitches)):
|
||||||
|
for j in range(i + 1, len(pitches)):
|
||||||
|
diff = pitches[i].pitch_difference(pitches[j])
|
||||||
|
ratio = diff.to_fraction()
|
||||||
|
distance = math.log2(ratio.numerator * ratio.denominator)
|
||||||
|
total_distance += distance
|
||||||
|
|
||||||
|
return 1.0 / (1.0 + total_distance)
|
||||||
|
|
||||||
def _factor_target_register(
|
def _factor_target_register(
|
||||||
self,
|
self,
|
||||||
path_chords: list[Chord],
|
path_chords: list[Chord],
|
||||||
|
|
@ -543,6 +578,7 @@ class Path:
|
||||||
"dca_hamiltonian": 0.0,
|
"dca_hamiltonian": 0.0,
|
||||||
"dca_voice_movement": 0.0,
|
"dca_voice_movement": 0.0,
|
||||||
"rgr_voice_movement": 0.0,
|
"rgr_voice_movement": 0.0,
|
||||||
|
"harmonic_compactness": 0.0,
|
||||||
"target_register": 0.0,
|
"target_register": 0.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,6 +587,7 @@ class Path:
|
||||||
w_hamiltonian = weights.get("weight_dca_hamiltonian", 1)
|
w_hamiltonian = weights.get("weight_dca_hamiltonian", 1)
|
||||||
w_dca = weights.get("weight_dca_voice_movement", 1)
|
w_dca = weights.get("weight_dca_voice_movement", 1)
|
||||||
w_rgr = weights.get("weight_rgr_voice_movement", 1)
|
w_rgr = weights.get("weight_rgr_voice_movement", 1)
|
||||||
|
w_compact = weights.get("weight_harmonic_compactness", 1)
|
||||||
w_target = weights.get("weight_target_register", 1)
|
w_target = weights.get("weight_target_register", 1)
|
||||||
|
|
||||||
for step in self.steps:
|
for step in self.steps:
|
||||||
|
|
@ -569,6 +606,9 @@ class Path:
|
||||||
influence["rgr_voice_movement"] += (
|
influence["rgr_voice_movement"] += (
|
||||||
norm.get("rgr_voice_movement") or 0
|
norm.get("rgr_voice_movement") or 0
|
||||||
) * w_rgr
|
) * w_rgr
|
||||||
|
influence["harmonic_compactness"] += (
|
||||||
|
norm.get("harmonic_compactness") or 0
|
||||||
|
) * w_compact
|
||||||
influence["target_register"] += (
|
influence["target_register"] += (
|
||||||
norm.get("target_register") or 0
|
norm.get("target_register") or 0
|
||||||
) * w_target
|
) * w_target
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue