diff --git a/README.md b/README.md index 2b6631c..e0eb861 100644 --- a/README.md +++ b/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-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 diff --git a/src/analyze.py b/src/analyze.py index 518718c..1ae953b 100644 --- a/src/analyze.py +++ b/src/analyze.py @@ -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, diff --git a/src/io.py b/src/io.py index 37e9585..1407732 100644 --- a/src/io.py +++ b/src/io.py @@ -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, } diff --git a/src/path.py b/src/path.py index 9cc5278..19d68b8 100644 --- a/src/path.py +++ b/src/path.py @@ -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 diff --git a/src/pathfinder.py b/src/pathfinder.py index 38116ba..1904efb 100644 --- a/src/pathfinder.py +++ b/src/pathfinder.py @@ -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: diff --git a/tests/test_compact_sets.py b/tests/test_compact_sets.py index aef44c8..ebb2842 100644 --- a/tests/test_compact_sets.py +++ b/tests/test_compact_sets.py @@ -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__":