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-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

View file

@ -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,

View file

@ -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,
} }

View file

@ -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

View file

@ -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:

View file

@ -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__":