Commit graph

41 commits

Author SHA1 Message Date
Michael Winter c682a1df02 Fix voice stay count to match master behavior
Changed voice stay count calculation in Path.step() to compare
by position (position i with position i) instead of tracking
voice identity. This makes dca_voice_movement factor behave
identically to master.
2026-03-16 02:27:21 +01:00
Michael Winter f2b785c98e Encapsulate voice-leading in Path.step()
- Move transposition, voice mapping into Path.step()
- Add node_visit_counts and voice_stay_count to each PathStep
- Fix voice tracking to use voice identity, not position
- Clean up unused last_graph_nodes code
- Full encapsulation: Path handles all voice-leading state
2026-03-16 01:11:15 +01:00
Michael Winter 66669de00f Add Path and Candidate classes for path state tracking
- Add src/path.py with Path and PathStep classes
- Path stores initial_chord, steps, weights_config
- PathStep stores graph_node, output_chord, transposition, movements, scores, candidates
- Refactor find_stochastic_path to use candidates approach
- Separate _build_candidates (raw scores) from _compute_weights
- Simplify return type to Path only (graph_chords available via property)
- Update io.py to use new Path API
2026-03-16 00:39:32 +01:00
Michael Winter b20f02b60f Fix DCA Hamiltonian: normalize by max instead of sum
- Change normalization from sum to max: visit_count / max^2
- This gives stronger discrimination toward unvisited nodes
- Weight 1 now gives 66.2% coverage (vs 63.6% baseline)
- Rename CLI: --weight-dca -> --weight-dca-voice-movement
- Rename CLI: --weight-hamiltonian -> --weight-dca-hamiltonian
- Restore DCA Hamiltonian display in analysis output
- Update README with new CLI names and factor descriptions
2026-03-15 13:04:00 +01:00
Michael Winter 218d7a55ff Refactor DCA factor: rename to DCA Voice Movement and DCA Hamiltonian
- Rename _factor_dca to _factor_dca_voice_movement (tracks voice pitch changes)
- Rename _factor_hamiltonian to _factor_dca_hamiltonian (tracks node visit counts)
- Update CLI: --weight-dca -> --weight-dca-voice-movement
- Update CLI: --weight-hamiltonian -> --weight-dca-hamiltonian
- Remove hamiltonian coverage from analysis (no longer tracking)
2026-03-15 12:20:12 +01:00
Michael Winter 8cdbe90501 Normalize edge weights with sum normalization
- Each soft factor is sum-normalized across all edge candidates
- Ensures factors compete equally regardless of native value ranges
- Skip factors with no variance (all candidates same value)
- Base weight of 1.0 ensures minimum probability
2026-03-15 12:04:08 +01:00
Michael Winter ebbb288844 Implement weighted contrary motion factor
- Half of moving voices should go one direction, half opposite
- Weighted by closeness to ideal half split
- factor = 1.0 - (distance_from_half / half)
- Works with odd (near-half) and even (exact half) voices
- Analysis shows contrary_motion_steps, percent, and avg_score
2026-03-15 11:31:24 +01:00
Michael Winter 559c868313 Fix Hamiltonian analysis and add DCA stay count metrics
- Save graph_path to output for accurate Hamiltonian tracking
- DCA analysis now shows avg/max voice stay counts
- Fix: use actual graph node hashes instead of rehashing transposed chords
2026-03-15 11:13:24 +01:00
Michael Winter 34a6ebfabd Add per-factor analysis metrics
- Melodic: violations, max violation, avg movement
- Contrary motion: steps with contrary, percentage
- DCA: avg voices changing, all voices change count/percent
- Hamiltonian: unique nodes, coverage percentage
- Target range: start/end cents, achieved percentage
- CLI: --stats now shows all metrics automatically
2026-03-15 10:48:06 +01:00
Michael Winter 16ecb192d1 Add analysis tool for chord sequences
- Add src/analyze.py: standalone analysis script
- Add --stats CLI flag to show stats after generation
- Analyze: melodic violations, target range %, voice changes
2026-03-15 10:42:22 +01:00
Michael Winter 0dbdfe02cb Start target range from first chord average
- Target now begins at average cents of starting chord
- Progress adds target_octaves on top of starting position
- More accurate register targeting
2026-03-14 03:24:04 +01:00
Michael Winter cc3c1ab971 Use average cents for target range tracking
- Replace cumulative_trans with average cents of actual chord
- More accurate register targeting using current chord position
- Test with max_path=150 shows reaching ~400 Hz target (2 octaves)
2026-03-14 03:09:46 +01:00
Michael Winter 2fe8737cfe Improve target range factor with relative distance
- Replace harsh 1/(1+distance) with bounded relative scoring
- Factor > 1.0 when moving toward target, < 1.0 when moving away
- Minimum factor 0.1 to avoid zero weights
- Test with max_path=150 shows reaching ~90% of 2-octave target
2026-03-14 03:05:23 +01:00
Michael Winter 737f1e4886 Refactor edge weights with DCA and CLI improvements
- Implement DCA (Dissonant Counterpoint Algorithm) to favor voice changes
- Track actual pitch changes in voice_stay_count (not graph indices)
- Add CLI weight arguments: --weight-melodic, --weight-contrary-motion,
  --weight-hamiltonian, --weight-dca, --weight-target-range
- DCA probability = (sum of stay_counts for changing voices) / (sum of all)
- Test with --weight-dca 100 shows frequent voice changes
2026-03-14 02:44:30 +01:00
Michael Winter 61149597c9 Add target range weight for rising register
- Add --target-range CLI option (in octaves)
- Implement target_range weight in PathFinder
- Add test for target range weight
- Add --max-path to README
2026-03-13 22:04:48 +01:00
Michael Winter 4ae83f857b Add tests for src module (Pitch, Chord, HarmonicSpace) 2026-03-13 19:11:50 +01:00
Michael Winter 8a03269298 Stop tracking session and AGENTS files 2026-03-13 19:05:29 +01:00
Michael Winter a17a7e448c Clean up: remove ipynb/txt, move scd to supercollider/, update gitignore 2026-03-13 19:02:24 +01:00
Michael Winter cd9c9ce037 Add README and GPLv3 LICENSE 2026-03-13 18:51:28 +01:00
Michael Winter 3e22346cf9 Add legacy code from compact_sets_sandbox 2026-03-13 18:45:13 +01:00
Michael Winter 0698d01d85 Refactor into src/ module with caching and CLI improvements
- Split monolithic compact_sets.py into modular src/ directory
- Add graph caching (pickle + JSON) with --cache-dir option
- Add --output-dir, --rebuild-cache, --no-cache CLI options
- Default seed is now None (random) instead of 42
- Add .gitignore entries for cache/ and output/
2026-03-13 18:38:38 +01:00
Michael Winter dd9df2ad33 Fix Hamiltonian to track untransposed graph nodes
The Hamiltonian weight was comparing transposed output chords
against untransposed graph nodes, so they never matched.
Now tracks graph_path separately for correct Hamiltonian check.
2026-03-13 05:07:24 +01:00
Michael Winter 40a8996b4e Rename _calc_symdiff_expanded to _calc_symdiff
Since projection now happens in generate_connected_sets, the function is simpler.
2026-03-13 04:35:17 +01:00
Michael Winter 2df55d8c16 Fix outdated docstring in _build_movement_maps
Update docstring to reflect index-based movement format: {src_idx: dest_idx}
instead of old pitch-based format.
2026-03-13 04:33:17 +01:00
Michael Winter a5efc548e5 Clean up code: remove unused imports, variables, and functions
- Remove duplicate 'permutations' import
- Remove unused 'choice' import
- Remove unused '_check_melodic_threshold' function
- Remove unused 'sorted_permutation' variable
- Update outdated comments
- Simplify _initialize_chords return value
2026-03-13 04:27:55 +01:00
Michael Winter cfab07da88 Sort chords in generate_connected_sets and simplify voice crossing detection
- Sort pitches by frequency after projection in generate_connected_sets
- This ensures all chords in graph have bass-to-soprano ordering
- Simplified voice crossing check: verify destination is sorted after movement
- Since source is sorted, just check if adjacent pairs remain in order
- Fixed movement map indexing bug in precomputed voice crossing detection
2026-03-13 04:16:48 +01:00
Michael Winter ba3ded82d8 Fix voice crossing detection
- Replace index-based voice crossing check with pitch ordering comparison
- Now properly detects when voice ordering changes between chords
- This was causing edges with voice crossing to be incorrectly allowed
2026-03-13 01:25:18 +01:00
Michael Winter c63b6cb1d4 Add frequency output file (fundamental=100Hz) 2026-03-13 01:11:18 +01:00
Michael Winter b89ff9c184 Add hamiltonian weight to favor unvisited nodes
- Add 'hamiltonian' option to weights config (default True)
- In edge weight calculation: boost unvisited nodes (10x), penalize visited (0.1x)
- Path now extends longer before getting stuck
2026-03-13 01:06:57 +01:00
Michael Winter f04b1baa49 Fix import: move permutations to top-level 2026-03-13 00:59:22 +01:00
Michael Winter fa19b7877f Clean up unused code
- Remove unused methods: generate_connected_sets_with_edges,
  build_graph_lattice_method, _compute_edge_data_fast, _wrap_pitch, _toCHS
- Remove unused import: product from itertools
- Remove old notebook files: compact_sets_optimized_2.py, compact_sets_optimized_2.ipynb
- Keep session-ses_328e.md for reference

compact_sets.py: 1450 -> 1067 lines
2026-03-13 00:55:27 +01:00
Michael Winter c44dd60e83 Fix transposition deduplication and rename expand to project
- Deduplicate transpositions in _find_valid_edges using set comprehension
  to avoid processing same transposition multiple times
- Edge count now matches notebook (1414 vs 2828)
- Rename expand() to project() for clarity (project to [1,2) range)
- Fix SyntaxWarnings in docstrings (escape backslashes)
2026-03-13 00:28:34 +01:00
Michael Winter 69f08814a9 Store edge properties at build time: cent_diffs, voice_crossing, is_directly_tunable
- Add _is_directly_tunable method to check if changing pitches are adjacent to staying pitch
- Modify _find_valid_edges to compute and return edge properties
- Store all properties in graph edges at build time
- Simplify _calculate_edge_weights to read from edge data
- Rename voice_crossing config to voice_crossing_allowed (False = reject crossing)
2026-03-12 20:06:14 +01:00
Michael Winter b7b95bb849 Remove movement_size weight as redundant with melodic threshold 2026-03-12 19:41:48 +01:00
Michael Winter e13e9ba3bc Add voice crossing check - reject edges where voices cross
- Movement map must be identity (0->0, 1->1, 2->2) to pass
- If any voice moves to a different position, weight = 0
2026-03-12 19:19:47 +01:00
Michael Winter d4cdd86e85 Add voice-leading preservation with index-to-index movement mapping
- Change movement map from {pitch: {destination, cents}} to {src_idx: dest_idx}
- Track voice mapping cumulatively in pathfinder
- Reorder output pitches according to voice mapping
- Update weight calculation to compute cent_diffs from index mapping
- Melodic threshold now correctly filters edges based on actual movements
2026-03-12 19:14:33 +01:00
Michael Winter 2b422d55fe Fix output to preserve internal tuple order
- Remove sorted_by_frequency from write functions
- Output now uses internal chord._pitches order instead of sorted
2026-03-12 18:18:40 +01:00
Michael Winter ccf90d19e1 Fix movement tracking and add melodic threshold weights
- Add movement tracking to edges: {source: {destination, cent_difference}}
- Fix movement map to handle multiple changing pitches with permutations
- Remove melodic threshold from graph building (apply in weight calculation)
- Add melodic_threshold_min/max to weight config
- Add CLI args --melodic-min and --melodic-max
2026-03-12 17:44:54 +01:00
Michael Winter aeb1fd9982 Fix path finding to use cumulative transpositions
- Track cumulative transposition across steps so output = destination + (T1 + T2 + ... + TN)
- Fix symdiff calculation to use expanded (transposed) pitches instead of collapsed
- Update CLI from --change to --symdiff-min/symdiff-max
2026-03-12 16:59:44 +01:00
Michael Winter f2bcd37287 Add elegant OOP implementation with CLI
- New compact_sets.py with Pitch, Chord, HarmonicSpace classes
- Voice leading graph with change parameter (instead of symdiff)
- CLI: --change, --dims, --chord-size, --max-path, --seed
- Tests for core functionality
- .gitignore for Python/pytest
2026-03-10 16:13:41 +01:00
Michael Winter 875ffb30f5 Initial commit: convert notebook to Python file 2026-03-10 14:40:15 +01:00