Rename 'target range' to 'target register' throughout
- CLI: --target-range -> --target-register - CLI: --weight-target-range -> --weight-target-register - Config keys: target_range -> target_register - Update README, tests, and all internal references
This commit is contained in:
parent
24709c3eda
commit
e4b6d2cfdc
10
README.md
10
README.md
|
|
@ -41,7 +41,7 @@ These constraints weigh edges. Set weight to 0 to disable.
|
||||||
--weight-contrary-motion N Weight for contrary motion (default: 0, 0=off)
|
--weight-contrary-motion N Weight for contrary motion (default: 0, 0=off)
|
||||||
--weight-dca-hamiltonian N Weight for visiting unvisited nodes (default: 1, 0=off)
|
--weight-dca-hamiltonian N Weight for visiting unvisited nodes (default: 1, 0=off)
|
||||||
--weight-dca-voice-movement N Weight for moving voices (default: 1, 0=off)
|
--weight-dca-voice-movement N Weight for moving voices (default: 1, 0=off)
|
||||||
--weight-target-range N Weight for target register (default: 1, 0=off)
|
--weight-target-register N Weight for target register (default: 1, 0=off)
|
||||||
|
|
||||||
### Melodic Range
|
### Melodic Range
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ These constraints weigh edges. Set weight to 0 to disable.
|
||||||
|
|
||||||
### Target Register
|
### Target Register
|
||||||
|
|
||||||
--target-range N Target register in octaves (default: disabled, 2 = 2 octaves rise)
|
--target-register N Target register in octaves (default: disabled, 2 = 2 octaves rise)
|
||||||
|
|
||||||
### Output Options
|
### Output Options
|
||||||
|
|
||||||
|
|
@ -93,10 +93,10 @@ The average pitch of all voices should rise toward the target (specified in octa
|
||||||
python -m src.io
|
python -m src.io
|
||||||
|
|
||||||
# Rising register to 2 octaves
|
# Rising register to 2 octaves
|
||||||
python -m src.io --target-range 2
|
python -m src.io --target-register 2
|
||||||
|
|
||||||
# Heavy target range, no DCA voice movement
|
# Heavy target register, no DCA voice movement
|
||||||
python -m src.io --target-range 2 --weight-target-range 10 --weight-dca-voice-movement 0
|
python -m src.io --target-register 2 --weight-target-register 10 --weight-dca-voice-movement 0
|
||||||
|
|
||||||
# Disable DCA Hamiltonian (allow revisiting nodes freely)
|
# Disable DCA Hamiltonian (allow revisiting nodes freely)
|
||||||
python -m src.io --weight-dca-hamiltonian 0
|
python -m src.io --weight-dca-hamiltonian 0
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ def analyze_chords(
|
||||||
Args:
|
Args:
|
||||||
chords: List of chords, each chord is a list of pitch dicts
|
chords: List of chords, each chord is a list of pitch dicts
|
||||||
config: Optional config with:
|
config: Optional config with:
|
||||||
- target_range_octaves: target octaves (default: 2.0)
|
- target_register_octaves: target octaves (default: 2.0)
|
||||||
- melodic_threshold_max: max cents per voice movement (default: 300)
|
- melodic_threshold_max: max cents per voice movement (default: 300)
|
||||||
- max_path: path length (default: 50)
|
- max_path: path length (default: 50)
|
||||||
- graph_nodes: total nodes in graph (optional, for Hamiltonian coverage)
|
- graph_nodes: total nodes in graph (optional, for Hamiltonian coverage)
|
||||||
|
|
@ -27,7 +27,7 @@ def analyze_chords(
|
||||||
if config is None:
|
if config is None:
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
target_octaves = config.get("target_range_octaves", 2.0)
|
target_octaves = config.get("target_register_octaves", 2.0)
|
||||||
melodic_max = config.get("melodic_threshold_max", 300)
|
melodic_max = config.get("melodic_threshold_max", 300)
|
||||||
max_path = config.get("max_path", 50)
|
max_path = config.get("max_path", 50)
|
||||||
graph_nodes = config.get("graph_nodes", None)
|
graph_nodes = config.get("graph_nodes", None)
|
||||||
|
|
@ -114,7 +114,7 @@ def analyze_chords(
|
||||||
else:
|
else:
|
||||||
voice_stay_counts[v] += 1 # Increment stay count
|
voice_stay_counts[v] += 1 # Increment stay count
|
||||||
|
|
||||||
# ========== Target Range ==========
|
# ========== Target Register ==========
|
||||||
target_cents = target_octaves * 1200
|
target_cents = target_octaves * 1200
|
||||||
|
|
||||||
if chords:
|
if chords:
|
||||||
|
|
@ -167,7 +167,7 @@ def analyze_chords(
|
||||||
# Hamiltonian
|
# Hamiltonian
|
||||||
"hamiltonian_unique_nodes": hamiltonian_unique_nodes,
|
"hamiltonian_unique_nodes": hamiltonian_unique_nodes,
|
||||||
"hamiltonian_coverage": hamiltonian_coverage,
|
"hamiltonian_coverage": hamiltonian_coverage,
|
||||||
# Target Range
|
# Target Register
|
||||||
"target_octaves": target_octaves,
|
"target_octaves": target_octaves,
|
||||||
"target_cents": target_cents,
|
"target_cents": target_cents,
|
||||||
"target_start_cents": start_avg,
|
"target_start_cents": start_avg,
|
||||||
|
|
@ -209,7 +209,7 @@ def format_analysis(metrics: dict) -> str:
|
||||||
lines.extend(
|
lines.extend(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
"--- Target Range ---",
|
"--- Target Register ---",
|
||||||
f"Target: {metrics['target_octaves']} octaves ({metrics['target_cents']:.0f} cents)",
|
f"Target: {metrics['target_octaves']} octaves ({metrics['target_cents']:.0f} cents)",
|
||||||
f"Start: {metrics['target_start_cents']:.0f} cents",
|
f"Start: {metrics['target_start_cents']:.0f} cents",
|
||||||
f"End: {metrics['target_end_cents']:.0f} cents",
|
f"End: {metrics['target_end_cents']:.0f} cents",
|
||||||
|
|
@ -250,10 +250,10 @@ def main():
|
||||||
help="Output raw JSON instead of formatted text",
|
help="Output raw JSON instead of formatted text",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--target-range",
|
"--target-register",
|
||||||
type=float,
|
type=float,
|
||||||
default=2.0,
|
default=2.0,
|
||||||
help="Target range in octaves (default: 2.0)",
|
help="Target register in octaves (default: 2.0)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--melodic-max",
|
"--melodic-max",
|
||||||
|
|
@ -281,7 +281,7 @@ def main():
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"target_range_octaves": args.target_range,
|
"target_register_octaves": args.target_register,
|
||||||
"melodic_threshold_max": args.melodic_max,
|
"melodic_threshold_max": args.melodic_max,
|
||||||
"max_path": args.max_path,
|
"max_path": args.max_path,
|
||||||
"graph_nodes": args.graph_nodes,
|
"graph_nodes": args.graph_nodes,
|
||||||
|
|
|
||||||
22
src/io.py
22
src/io.py
|
|
@ -274,10 +274,10 @@ def main():
|
||||||
help="Maximum cents for any pitch movement (0 = no maximum)",
|
help="Maximum cents for any pitch movement (0 = no maximum)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--target-range",
|
"--target-register",
|
||||||
type=float,
|
type=float,
|
||||||
default=0,
|
default=0,
|
||||||
help="Target range in octaves for rising register (default: disabled, 2 = two octaves)",
|
help="Target register in octaves (default: disabled, 2 = two octaves)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--allow-voice-crossing",
|
"--allow-voice-crossing",
|
||||||
|
|
@ -314,10 +314,10 @@ def main():
|
||||||
help="Weight for DCA voice movement factor - favors voices that stay long to change (0=disabled, default: 1)",
|
help="Weight for DCA voice movement factor - favors voices that stay long to change (0=disabled, default: 1)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--weight-target-range",
|
"--weight-target-register",
|
||||||
type=float,
|
type=float,
|
||||||
default=1,
|
default=1,
|
||||||
help="Weight for target range factor (0=disabled, default: 1)",
|
help="Weight for target register factor (0=disabled, default: 1)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dims", type=int, default=7, help="Number of prime dimensions (4, 5, 7, or 8)"
|
"--dims", type=int, default=7, help="Number of prime dimensions (4, 5, 7, or 8)"
|
||||||
|
|
@ -437,13 +437,13 @@ def main():
|
||||||
weights_config["weight_dca_hamiltonian"] = args.weight_dca_hamiltonian
|
weights_config["weight_dca_hamiltonian"] = args.weight_dca_hamiltonian
|
||||||
weights_config["weight_dca_voice_movement"] = args.weight_dca_voice_movement
|
weights_config["weight_dca_voice_movement"] = args.weight_dca_voice_movement
|
||||||
|
|
||||||
# Target range
|
# Target register
|
||||||
if args.target_range > 0:
|
if args.target_register > 0:
|
||||||
weights_config["target_range"] = True
|
weights_config["target_register"] = True
|
||||||
weights_config["target_range_octaves"] = args.target_range
|
weights_config["target_register_octaves"] = args.target_register
|
||||||
weights_config["weight_target_range"] = args.weight_target_range
|
weights_config["weight_target_register"] = args.weight_target_register
|
||||||
else:
|
else:
|
||||||
weights_config["weight_target_range"] = 0 # disabled
|
weights_config["weight_target_register"] = 0 # disabled
|
||||||
|
|
||||||
weights_config["max_path"] = args.max_path
|
weights_config["max_path"] = args.max_path
|
||||||
|
|
||||||
|
|
@ -487,7 +487,7 @@ def main():
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"melodic_threshold_max": args.melodic_max,
|
"melodic_threshold_max": args.melodic_max,
|
||||||
"target_range_octaves": args.target_range,
|
"target_register_octaves": args.target_register,
|
||||||
"max_path": args.max_path,
|
"max_path": args.max_path,
|
||||||
"graph_nodes": graph.number_of_nodes() if graph else None,
|
"graph_nodes": graph.number_of_nodes() if graph else None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/path.py
24
src/path.py
|
|
@ -131,7 +131,7 @@ class Path:
|
||||||
sustain_before,
|
sustain_before,
|
||||||
self.weights_config,
|
self.weights_config,
|
||||||
),
|
),
|
||||||
"target_range": self._factor_target_range(
|
"target_register": self._factor_target_register(
|
||||||
path_chords,
|
path_chords,
|
||||||
destination_chord,
|
destination_chord,
|
||||||
self.weights_config,
|
self.weights_config,
|
||||||
|
|
@ -185,7 +185,7 @@ class Path:
|
||||||
contrary_values = [c.scores.get("contrary_motion", 0) for c in candidates]
|
contrary_values = [c.scores.get("contrary_motion", 0) for c in candidates]
|
||||||
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]
|
||||||
target_values = [c.scores.get("target_range", 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:
|
||||||
"""Normalize values to sum to 1. Returns None if no discrimination."""
|
"""Normalize values to sum to 1. Returns None if no discrimination."""
|
||||||
|
|
@ -226,7 +226,7 @@ class Path:
|
||||||
if dca_norm:
|
if dca_norm:
|
||||||
w *= dca_norm[i] * config.get("weight_dca_voice_movement", 1)
|
w *= dca_norm[i] * config.get("weight_dca_voice_movement", 1)
|
||||||
if target_norm:
|
if target_norm:
|
||||||
w *= target_norm[i] * config.get("weight_target_range", 1)
|
w *= target_norm[i] * config.get("weight_target_register", 1)
|
||||||
|
|
||||||
step.weight = w**16
|
step.weight = w**16
|
||||||
weights.append(w)
|
weights.append(w)
|
||||||
|
|
@ -237,7 +237,7 @@ class Path:
|
||||||
"contrary_motion": contrary_norm[i] if contrary_norm else None,
|
"contrary_motion": contrary_norm[i] if contrary_norm else None,
|
||||||
"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,
|
||||||
"target_range": target_norm[i] if target_norm else None,
|
"target_register": target_norm[i] if target_norm else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
return weights
|
return weights
|
||||||
|
|
@ -350,23 +350,23 @@ class Path:
|
||||||
|
|
||||||
return sum_changing
|
return sum_changing
|
||||||
|
|
||||||
def _factor_target_range(
|
def _factor_target_register(
|
||||||
self,
|
self,
|
||||||
path_chords: list[Chord],
|
path_chords: list[Chord],
|
||||||
destination_chord: Chord,
|
destination_chord: Chord,
|
||||||
config: dict,
|
config: dict,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Returns factor based on movement toward target."""
|
"""Returns factor based on movement toward target."""
|
||||||
if config.get("weight_target_range", 1) == 0:
|
if config.get("weight_target_register", 1) == 0:
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
if not config.get("target_range", False):
|
if not config.get("target_register", False):
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
if len(path_chords) == 0:
|
if len(path_chords) == 0:
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
target_octaves = config.get("target_range_octaves", 2.0)
|
target_octaves = config.get("target_register_octaves", 2.0)
|
||||||
max_path = config.get("max_path", 50)
|
max_path = config.get("max_path", 50)
|
||||||
target_cents = target_octaves * 1200
|
target_cents = target_octaves * 1200
|
||||||
|
|
||||||
|
|
@ -463,14 +463,14 @@ class Path:
|
||||||
"contrary_motion": 0.0,
|
"contrary_motion": 0.0,
|
||||||
"dca_hamiltonian": 0.0,
|
"dca_hamiltonian": 0.0,
|
||||||
"dca_voice_movement": 0.0,
|
"dca_voice_movement": 0.0,
|
||||||
"target_range": 0.0,
|
"target_register": 0.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
w_melodic = weights.get("weight_melodic", 1)
|
w_melodic = weights.get("weight_melodic", 1)
|
||||||
w_contrary = weights.get("weight_contrary_motion", 0)
|
w_contrary = weights.get("weight_contrary_motion", 0)
|
||||||
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_target = weights.get("weight_target_range", 1)
|
w_target = weights.get("weight_target_register", 1)
|
||||||
|
|
||||||
for step in self.steps:
|
for step in self.steps:
|
||||||
norm = step.normalized_scores
|
norm = step.normalized_scores
|
||||||
|
|
@ -485,6 +485,8 @@ class Path:
|
||||||
influence["dca_voice_movement"] += (
|
influence["dca_voice_movement"] += (
|
||||||
norm.get("dca_voice_movement") or 0
|
norm.get("dca_voice_movement") or 0
|
||||||
) * w_dca
|
) * w_dca
|
||||||
influence["target_range"] += (norm.get("target_range") or 0) * w_target
|
influence["target_register"] += (
|
||||||
|
norm.get("target_register") or 0
|
||||||
|
) * w_target
|
||||||
|
|
||||||
return influence
|
return influence
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,8 @@ class PathFinder:
|
||||||
"melodic_threshold_max": 500,
|
"melodic_threshold_max": 500,
|
||||||
"hamiltonian": True,
|
"hamiltonian": True,
|
||||||
"dca": 2.0,
|
"dca": 2.0,
|
||||||
"target_range": False,
|
"target_register": False,
|
||||||
"target_range_octaves": 2.0,
|
"target_register_octaves": 2.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
def is_hamiltonian(self, path: list["Chord"]) -> bool:
|
def is_hamiltonian(self, path: list["Chord"]) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -199,8 +199,8 @@ class TestCLI:
|
||||||
# For now just verify the module can be imported
|
# For now just verify the module can be imported
|
||||||
assert main is not None
|
assert main is not None
|
||||||
|
|
||||||
def test_target_range_weight(self):
|
def test_target_register_weight(self):
|
||||||
"""Test that target range weight can be enabled and influences path generation."""
|
"""Test that target register weight can be enabled and influences path generation."""
|
||||||
from src.harmonic_space import HarmonicSpace
|
from src.harmonic_space import HarmonicSpace
|
||||||
from src.pathfinder import PathFinder
|
from src.pathfinder import PathFinder
|
||||||
|
|
||||||
|
|
@ -210,17 +210,17 @@ class TestCLI:
|
||||||
|
|
||||||
path_finder = PathFinder(graph)
|
path_finder = PathFinder(graph)
|
||||||
|
|
||||||
# Test with target range disabled (default)
|
# Test with target register disabled (default)
|
||||||
config_no_target = path_finder._default_weights_config()
|
config_no_target = path_finder._default_weights_config()
|
||||||
assert config_no_target.get("target_range") is False
|
assert config_no_target.get("target_register") is False
|
||||||
|
|
||||||
# Test with target range enabled
|
# Test with target register enabled
|
||||||
config_target = path_finder._default_weights_config()
|
config_target = path_finder._default_weights_config()
|
||||||
config_target["target_range"] = True
|
config_target["target_register"] = True
|
||||||
config_target["target_range_octaves"] = 2.0
|
config_target["target_register_octaves"] = 2.0
|
||||||
|
|
||||||
assert config_target.get("target_range") is True
|
assert config_target.get("target_register") is True
|
||||||
assert config_target.get("target_range_octaves") == 2.0
|
assert config_target.get("target_register_octaves") == 2.0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue