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:
Michael Winter 2026-03-17 14:11:21 +01:00
parent 24709c3eda
commit e4b6d2cfdc
6 changed files with 48 additions and 46 deletions

View file

@ -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-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-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
@ -50,7 +50,7 @@ These constraints weigh edges. Set weight to 0 to disable.
### 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
@ -93,10 +93,10 @@ The average pitch of all voices should rise toward the target (specified in octa
python -m src.io
# 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
python -m src.io --target-range 2 --weight-target-range 10 --weight-dca-voice-movement 0
# Heavy target register, no DCA voice movement
python -m src.io --target-register 2 --weight-target-register 10 --weight-dca-voice-movement 0
# Disable DCA Hamiltonian (allow revisiting nodes freely)
python -m src.io --weight-dca-hamiltonian 0

View file

@ -15,7 +15,7 @@ def analyze_chords(
Args:
chords: List of chords, each chord is a list of pitch dicts
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)
- max_path: path length (default: 50)
- graph_nodes: total nodes in graph (optional, for Hamiltonian coverage)
@ -27,7 +27,7 @@ def analyze_chords(
if config is None:
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)
max_path = config.get("max_path", 50)
graph_nodes = config.get("graph_nodes", None)
@ -114,7 +114,7 @@ def analyze_chords(
else:
voice_stay_counts[v] += 1 # Increment stay count
# ========== Target Range ==========
# ========== Target Register ==========
target_cents = target_octaves * 1200
if chords:
@ -167,7 +167,7 @@ def analyze_chords(
# Hamiltonian
"hamiltonian_unique_nodes": hamiltonian_unique_nodes,
"hamiltonian_coverage": hamiltonian_coverage,
# Target Range
# Target Register
"target_octaves": target_octaves,
"target_cents": target_cents,
"target_start_cents": start_avg,
@ -209,7 +209,7 @@ def format_analysis(metrics: dict) -> str:
lines.extend(
[
"",
"--- Target Range ---",
"--- Target Register ---",
f"Target: {metrics['target_octaves']} octaves ({metrics['target_cents']:.0f} cents)",
f"Start: {metrics['target_start_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",
)
parser.add_argument(
"--target-range",
"--target-register",
type=float,
default=2.0,
help="Target range in octaves (default: 2.0)",
help="Target register in octaves (default: 2.0)",
)
parser.add_argument(
"--melodic-max",
@ -281,7 +281,7 @@ def main():
return 1
config = {
"target_range_octaves": args.target_range,
"target_register_octaves": args.target_register,
"melodic_threshold_max": args.melodic_max,
"max_path": args.max_path,
"graph_nodes": args.graph_nodes,

View file

@ -274,10 +274,10 @@ def main():
help="Maximum cents for any pitch movement (0 = no maximum)",
)
parser.add_argument(
"--target-range",
"--target-register",
type=float,
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(
"--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)",
)
parser.add_argument(
"--weight-target-range",
"--weight-target-register",
type=float,
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(
"--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_voice_movement"] = args.weight_dca_voice_movement
# Target range
if args.target_range > 0:
weights_config["target_range"] = True
weights_config["target_range_octaves"] = args.target_range
weights_config["weight_target_range"] = args.weight_target_range
# Target register
if args.target_register > 0:
weights_config["target_register"] = True
weights_config["target_register_octaves"] = args.target_register
weights_config["weight_target_register"] = args.weight_target_register
else:
weights_config["weight_target_range"] = 0 # disabled
weights_config["weight_target_register"] = 0 # disabled
weights_config["max_path"] = args.max_path
@ -487,7 +487,7 @@ def main():
config = {
"melodic_threshold_max": args.melodic_max,
"target_range_octaves": args.target_range,
"target_register_octaves": args.target_register,
"max_path": args.max_path,
"graph_nodes": graph.number_of_nodes() if graph else None,
}

View file

@ -131,7 +131,7 @@ class Path:
sustain_before,
self.weights_config,
),
"target_range": self._factor_target_range(
"target_register": self._factor_target_register(
path_chords,
destination_chord,
self.weights_config,
@ -185,7 +185,7 @@ class Path:
contrary_values = [c.scores.get("contrary_motion", 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]
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:
"""Normalize values to sum to 1. Returns None if no discrimination."""
@ -226,7 +226,7 @@ class Path:
if dca_norm:
w *= dca_norm[i] * config.get("weight_dca_voice_movement", 1)
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
weights.append(w)
@ -237,7 +237,7 @@ class Path:
"contrary_motion": contrary_norm[i] if contrary_norm else None,
"dca_hamiltonian": hamiltonian_norm[i] if hamiltonian_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
@ -350,23 +350,23 @@ class Path:
return sum_changing
def _factor_target_range(
def _factor_target_register(
self,
path_chords: list[Chord],
destination_chord: Chord,
config: dict,
) -> float:
"""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
if not config.get("target_range", False):
if not config.get("target_register", False):
return 1.0
if len(path_chords) == 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)
target_cents = target_octaves * 1200
@ -463,14 +463,14 @@ class Path:
"contrary_motion": 0.0,
"dca_hamiltonian": 0.0,
"dca_voice_movement": 0.0,
"target_range": 0.0,
"target_register": 0.0,
}
w_melodic = weights.get("weight_melodic", 1)
w_contrary = weights.get("weight_contrary_motion", 0)
w_hamiltonian = weights.get("weight_dca_hamiltonian", 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:
norm = step.normalized_scores
@ -485,6 +485,8 @@ class Path:
influence["dca_voice_movement"] += (
norm.get("dca_voice_movement") or 0
) * 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

View file

@ -124,8 +124,8 @@ class PathFinder:
"melodic_threshold_max": 500,
"hamiltonian": True,
"dca": 2.0,
"target_range": False,
"target_range_octaves": 2.0,
"target_register": False,
"target_register_octaves": 2.0,
}
def is_hamiltonian(self, path: list["Chord"]) -> bool:

View file

@ -199,8 +199,8 @@ class TestCLI:
# For now just verify the module can be imported
assert main is not None
def test_target_range_weight(self):
"""Test that target range weight can be enabled and influences path generation."""
def test_target_register_weight(self):
"""Test that target register weight can be enabled and influences path generation."""
from src.harmonic_space import HarmonicSpace
from src.pathfinder import PathFinder
@ -210,17 +210,17 @@ class TestCLI:
path_finder = PathFinder(graph)
# Test with target range disabled (default)
# Test with target register disabled (default)
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["target_range"] = True
config_target["target_range_octaves"] = 2.0
config_target["target_register"] = True
config_target["target_register_octaves"] = 2.0
assert config_target.get("target_range") is True
assert config_target.get("target_range_octaves") == 2.0
assert config_target.get("target_register") is True
assert config_target.get("target_register_octaves") == 2.0
if __name__ == "__main__":