From ba3ded82d8ddc4f1cb75f6ee6ca04ecf726c3fdf Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Fri, 13 Mar 2026 01:25:18 +0100 Subject: [PATCH] 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 --- compact_sets.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/compact_sets.py b/compact_sets.py index 7c258a5..3aabbbb 100644 --- a/compact_sets.py +++ b/compact_sets.py @@ -540,11 +540,20 @@ class HarmonicSpace: cents = abs(src_pitch.to_cents() - dst_pitch.to_cents()) cent_diffs.append(cents) - # Check voice_crossing: True if any voice moves to different position - num_voices = len(c1.pitches) - voice_crossing = not all( - movements.get(i, i) == i for i in range(num_voices) - ) + # Check voice_crossing: compare pitch ordering before/after + # Voice crossing = True if ordering changes (e.g., bass below tenor -> tenor below bass) + source = list(c1.pitches) + ordered_source = sorted(source, key=lambda p: p.to_fraction()) + source_order = [ordered_source.index(p) for p in source] + + # Destination pitches: transpose back to get sounding pitches + destination = [ + c2_transposed.pitches[movements[p]] for p in range(len(source)) + ] + ordered_destination = sorted(destination, key=lambda p: p.to_fraction()) + destination_order = [ordered_destination.index(p) for p in destination] + + voice_crossing = source_order != destination_order # Check is_directly_tunable: changing pitches are adjacent to staying pitch is_directly_tunable = self._is_directly_tunable(