Generation
ci / checks (push) Successful in 47m20s

This commit is contained in:
khodowany
2026-09-30 01:54:59 -05:00
parent deaa0468a7
commit 8a23fa0fc2
6 changed files with 50 additions and 34 deletions
+40 -22
View File
@@ -7,6 +7,7 @@ import generator.guitar
import generator.harmony
import generator.model
import generator.musicxml
import generator.rhythm
import tests.rhythm_fixtures
import tests.test_musicxml
import tests.test_parts
@@ -42,14 +43,14 @@ class VoicingTests(unittest.TestCase):
self.assertEqual(max(p.midi for p in best), 65)
self.assertNotIn(1, {p.midi % 12 for p in best})
self.assertTrue(any(1 in {p.midi % 12 for p in c} and max(p.midi for p in c) > 65 for c in candidates))
def test_lower_position_can_balance_a_wider_span_without_covering_melody(self):
def test_compact_chord_can_beat_lower_frets_without_covering_melody(self):
candidates = ranked('CM7', (67,))
self.assertEqual(candidates[0], grip((2, 4, 1, 3)))
self.assertIn(grip((5, 4, 5, 3)), candidates)
self.assertEqual(candidates[0], grip((5, 4, 5, 3)))
self.assertIn(grip((2, 4, 1, 3)), candidates)
rooted = generator.guitar.voicing_cost(grip((2, 4, 1, 3)), 'electric', (0, 4, 7, 11), (67,))
rootless = generator.guitar.voicing_cost(grip((5, 4, 5, 3)), 'electric', (0, 4, 7, 11), (67,))
self.assertEqual(rooted.effort, rootless.effort)
self.assertLess(rooted, rootless)
self.assertEqual((rooted.effort, rootless.effort), (19, 15))
self.assertLess(rootless, rooted)
def test_essential_extensions_are_never_relaxed_to_add_the_root(self):
for symbol in ('C7b9', 'FM7#11', 'C13', 'Cdim7'):
definition = generator.harmony.parse_symbol(symbol)
@@ -81,30 +82,47 @@ class VoicingTests(unittest.TestCase):
self.assertEqual(generator.guitar.voicing_cost(grip((fret,) * 4)).effort, 2 * max(0, fret - 12))
crossing = grip((11, 12, 13, 14))
self.assertEqual(generator.guitar.voicing_cost(crossing).effort, 7)
self.assertEqual(generator.guitar.voicing_cost(crossing, 'electric').effort, 21)
self.assertEqual(generator.guitar.voicing_cost(crossing, 'acoustic').effort, 18)
self.assertEqual(generator.guitar.voicing_cost(crossing, 'acoustic').simplicity, 15)
def test_span_and_acoustic_simplicity_costs_remain_unchanged(self):
for profile in ('electric', 'acoustic'):
self.assertEqual(generator.guitar.voicing_cost(crossing, profile).effort, 33)
def test_chord_shape_and_height_share_one_effort_cost(self):
shape = grip((1, 3, 4, 5))
self.assertEqual(generator.guitar.voicing_cost(shape).effort, 4)
self.assertEqual(generator.guitar.voicing_cost(shape, 'electric').effort, 9)
self.assertEqual(generator.guitar.voicing_cost(shape, 'acoustic').effort, 5)
self.assertEqual(generator.guitar.voicing_cost(shape, 'acoustic').simplicity, 18)
for profile in ('electric', 'acoustic'):
self.assertEqual(generator.guitar.voicing_cost(shape, profile).effort, 23)
for profile in ('', 'electric', 'acoustic'):
self.assertLess(generator.guitar.voicing_cost(grip((4, 4, 4, 4)), profile), generator.guitar.voicing_cost(grip((2, 4, 1, 3)), profile))
def test_lower_acoustic_half_diminished_and_dominant_grips_win(self):
def test_distinct_frets_add_difficulty_even_with_the_same_span(self):
simple, varied = grip((3, 3, 5, 5)), grip((3, 4, 5, 5))
for profile in ('electric', 'acoustic'):
self.assertEqual(generator.guitar.voicing_cost(simple, profile).effort, 13)
self.assertEqual(generator.guitar.voicing_cost(varied, profile).effort, 15)
self.assertEqual(generator.guitar.voicing_cost(simple).effort, generator.guitar.voicing_cost(varied).effort)
def test_compact_mid_neck_acoustic_pair_beats_high_and_stretched_low_pairs(self):
high = [grip((12, 12, 12, 11), (6, 4, 3, 2)), grip((12, 11, 12, 11), (6, 4, 3, 2))]
low = [grip((7, 8, 7, 8), (5, 4, 3, 2)), grip((7, 8, 6, 8), (5, 4, 3, 2))]
for symbol, upper, lower in zip(('Em7b5', 'A7b9'), high, low):
requested = [grip((7, 8, 7, 8), (5, 4, 3, 2)), grip((7, 8, 6, 8), (5, 4, 3, 2))]
low = [grip((6, 5, 3, 3), (6, 4, 3, 2)), grip((5, 5, 3, 2), (6, 4, 3, 2))]
for symbol, upper, middle, lower in zip(('Em7b5', 'A7b9'), high, requested, low):
requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol), 'acoustic')
candidates = generator.guitar.chord_positions(requirements, profile='acoustic')
self.assertIn(upper, candidates)
self.assertIn(lower, candidates)
self.assertEqual({p.midi % 12 for p in upper}, {p.midi % 12 for p in lower})
self.assertEqual(min(p.midi for p in upper), min(p.midi for p in lower))
self.assertLess(generator.guitar.voicing_cost(lower, 'acoustic', requirements.allowed), generator.guitar.voicing_cost(upper, 'acoustic', requirements.allowed))
costs = [sum(generator.guitar.voicing_cost(s, 'acoustic').effort for s in pair) + generator.guitar.acoustic_movement(*pair) for pair in (low, high)]
self.assertEqual(costs, [17, 25])
for shape in (upper, middle, lower):
self.assertIn(shape, candidates)
self.assertEqual({p.midi % 12 for p in upper}, {p.midi % 12 for p in middle})
self.assertEqual(min(p.midi for p in upper), min(p.midi for p in middle))
costs = [sum(generator.guitar.voicing_cost(s, 'acoustic').effort for s in pair) + 2 * generator.guitar.acoustic_movement(*pair) for pair in (requested, high, low)]
self.assertEqual(costs, [33, 36, 41])
def test_real_autumn_leaves_lead_in_uses_requested_compact_grips(self):
source = generator.musicxml.load_musicxml(pathlib.Path('data/songs/autumn-leaves.xml'))
before = copy.deepcopy(source)
result = generator.arrange.generate.arrange_document(source, 'acoustic', rhythms=generator.rhythm.load_library(pathlib.Path('data/rhythms')))
generator.model.validate_document(result)
measures = result['staves'][0]['measures'][:2]
self.assertEqual(measures[0]['section_label'], 'Rhythm Lead-in')
for measure, symbol, expected in zip(measures, ('Em7b5', 'A7b9'), (((5, 7), (4, 8), (3, 7), (2, 8)), ((5, 7), (4, 8), (3, 6), (2, 8)))):
with self.subTest(chord=symbol):
self.assertEqual(generator.harmony.symbol(measure['harmonies'][0]), symbol)
positions = {(p['tablature']['string'], p['tablature']['fret']) for v in measure['voices'] for n in v['notes'] for p in n['pitches']}
self.assertEqual(tuple(sorted(positions, reverse=True)), expected)
self.assertEqual(source, before)
def test_acoustic_simpler_three_note_shape_can_beat_four_notes(self):
candidates = ranked('Csus', profile='acoustic')
best = candidates[0]