+40
-30
@@ -8,7 +8,9 @@ import unittest
|
||||
import unittest.mock
|
||||
|
||||
import generator.__main__
|
||||
import generator.acoustic_voicing
|
||||
import generator.arrange.generate
|
||||
import generator.fretboard
|
||||
import generator.guitar
|
||||
import generator.harmony
|
||||
import generator.model
|
||||
@@ -22,22 +24,28 @@ import tests.test_musicxml
|
||||
|
||||
class GuitarTests(unittest.TestCase):
|
||||
def test_fret_boundaries_and_span(self):
|
||||
self.assertFalse(generator.guitar.positions_for_pitch(fractions.Fraction(40)))
|
||||
self.assertIn(generator.guitar.TabPosition(6, 1, 41), generator.guitar.positions_for_pitch(fractions.Fraction(41)))
|
||||
self.assertIn(generator.guitar.TabPosition(1, 20, 84), generator.guitar.positions_for_pitch(fractions.Fraction(84)))
|
||||
self.assertFalse(generator.guitar.positions_for_pitch(fractions.Fraction(85)))
|
||||
self.assertFalse(generator.guitar.positions_for_pitch(fractions.Fraction(129, 2)))
|
||||
self.assertTrue(generator.guitar.within_fret_span((generator.guitar.TabPosition(4, 11, 61), generator.guitar.TabPosition(3, 12, 67), generator.guitar.TabPosition(2, 14, 73))))
|
||||
self.assertFalse(generator.guitar.within_fret_span((generator.guitar.TabPosition(6, 0, 40), generator.guitar.TabPosition(1, 20, 84))))
|
||||
self.assertFalse(generator.guitar.within_fret_span((generator.guitar.TabPosition(4, 10, 60), generator.guitar.TabPosition(1, 20, 84))))
|
||||
self.assertFalse(generator.fretboard.positions_for_pitch(fractions.Fraction(40)))
|
||||
self.assertIn(generator.fretboard.TabPosition(6, 1, 41), generator.fretboard.positions_for_pitch(fractions.Fraction(41)))
|
||||
self.assertIn(generator.fretboard.TabPosition(1, 20, 84), generator.fretboard.positions_for_pitch(fractions.Fraction(84)))
|
||||
self.assertFalse(generator.fretboard.positions_for_pitch(fractions.Fraction(85)))
|
||||
self.assertFalse(generator.fretboard.positions_for_pitch(fractions.Fraction(129, 2)))
|
||||
self.assertTrue(generator.fretboard.within_fret_span((generator.fretboard.TabPosition(4, 11, 61), generator.fretboard.TabPosition(3, 12, 67), generator.fretboard.TabPosition(2, 14, 73))))
|
||||
self.assertFalse(generator.fretboard.within_fret_span((generator.fretboard.TabPosition(6, 0, 40), generator.fretboard.TabPosition(1, 20, 84))))
|
||||
self.assertFalse(generator.fretboard.within_fret_span((generator.fretboard.TabPosition(4, 10, 60), generator.fretboard.TabPosition(1, 20, 84))))
|
||||
def test_fretted_positions_keep_span_and_proximity_scoring(self):
|
||||
for frets, valid in (((1, 2, 3), True), ((1, 5), True), ((1, 6), False), ((12, 1, 11), False), ((3, 3), True)):
|
||||
for frets, valid in (((1, 2, 3), True), ((1, 4), True), ((1, 5), False), ((7, 10), True), ((7, 11), False), ((12, 1, 11), False), ((3, 3), True)):
|
||||
with self.subTest(frets=frets):
|
||||
positions = tuple(generator.guitar.TabPosition(6 - i, fret, 0) for i, fret in enumerate(frets))
|
||||
self.assertEqual(generator.guitar.within_fret_span(positions), valid)
|
||||
positions = tuple(generator.fretboard.TabPosition(6 - i, fret, 0) for i, fret in enumerate(frets))
|
||||
self.assertEqual(generator.fretboard.within_fret_span(positions), valid)
|
||||
self.assertEqual(generator.guitar.compactness(positions), max(frets) - min(frets))
|
||||
self.assertEqual(generator.guitar.position_center(positions)[0], min(frets) + max(frets))
|
||||
self.assertTrue(generator.guitar.within_fret_span(()))
|
||||
self.assertTrue(generator.fretboard.within_fret_span(()))
|
||||
def test_held_notes_obey_the_shared_three_fret_limit(self):
|
||||
held = {'held': generator.fretboard.TabPosition(4, 7, 57)}
|
||||
for fret, valid in ((10, True), (11, False)):
|
||||
with self.subTest(fret=fret):
|
||||
pending = [('attack', (generator.fretboard.TabPosition(2, fret, 59 + fret),))]
|
||||
self.assertEqual(bool(list(generator.guitar.extend_positions(held, pending))), valid)
|
||||
def test_low_open_e_fails_without_octave_fitting_or_mutation(self):
|
||||
document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', octave=2)))
|
||||
before = copy.deepcopy(document)
|
||||
@@ -45,8 +53,8 @@ class GuitarTests(unittest.TestCase):
|
||||
generator.guitar.place_guitar(document)
|
||||
self.assertEqual(document, before)
|
||||
def test_open_string_pitch_uses_a_fretted_alternative(self):
|
||||
for _, midi in generator.guitar.STANDARD_TUNING[1:]:
|
||||
positions = generator.guitar.positions_for_pitch(fractions.Fraction(midi))
|
||||
for _, midi in generator.fretboard.STANDARD_TUNING[1:]:
|
||||
positions = generator.fretboard.positions_for_pitch(fractions.Fraction(midi))
|
||||
self.assertTrue(positions)
|
||||
self.assertTrue(all(p.fret >= 1 and p.midi == midi for p in positions))
|
||||
document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', octave=4)))
|
||||
@@ -67,7 +75,7 @@ class GuitarTests(unittest.TestCase):
|
||||
def test_theory_controls_omissions_and_bass(self):
|
||||
for symbol in ('CM', 'G7b9', 'FM7#11/A', 'CmM7', 'Cdim7', 'C13', 'Csus', 'Cmadd9'):
|
||||
with self.subTest(symbol=symbol):
|
||||
requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol))
|
||||
requirements = generator.fretboard.chord_requirements(generator.harmony.parse_symbol(symbol))
|
||||
candidates = generator.guitar.chord_positions(requirements)
|
||||
self.assertTrue(candidates)
|
||||
for candidate in candidates:
|
||||
@@ -75,29 +83,29 @@ class GuitarTests(unittest.TestCase):
|
||||
self.assertTrue(requirements.covers(covered))
|
||||
self.assertTrue(covered <= set(requirements.allowed))
|
||||
self.assertEqual(len(candidate), len({p.string_number for p in candidate}))
|
||||
self.assertTrue(generator.guitar.within_fret_span(candidate))
|
||||
self.assertTrue(generator.fretboard.within_fret_span(candidate))
|
||||
self.assertTrue(all(1 <= p.fret <= 20 for p in candidate))
|
||||
if requirements.basses:
|
||||
self.assertIn(min(p.midi for p in candidate) % 12, requirements.basses)
|
||||
self.assertIn(2, generator.guitar.chord_requirements(generator.harmony.parse_symbol('Cmadd9')).required)
|
||||
self.assertNotIn(7, generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7')).required)
|
||||
self.assertEqual(generator.guitar.chord_requirements(generator.harmony.parse_symbol('CM')).foundation, (0, 7))
|
||||
self.assertEqual(generator.guitar.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 0}).basses, (0,))
|
||||
self.assertEqual(generator.guitar.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 1}).basses, (4,))
|
||||
self.assertTrue(any(p.fret > 12 for c in generator.guitar.chord_positions(generator.guitar.chord_requirements(generator.harmony.parse_symbol('CM'))) for p in c))
|
||||
self.assertIn(2, generator.fretboard.chord_requirements(generator.harmony.parse_symbol('Cmadd9')).required)
|
||||
self.assertNotIn(7, generator.fretboard.chord_requirements(generator.harmony.parse_symbol('C7')).required)
|
||||
self.assertEqual(generator.fretboard.chord_requirements(generator.harmony.parse_symbol('CM')).foundation, (0, 7))
|
||||
self.assertEqual(generator.fretboard.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 0}).basses, (0,))
|
||||
self.assertEqual(generator.fretboard.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 1}).basses, (4,))
|
||||
self.assertTrue(any(p.fret > 12 for c in generator.guitar.chord_positions(generator.fretboard.chord_requirements(generator.harmony.parse_symbol('CM'))) for p in c))
|
||||
def test_root_or_fifth_is_required_for_every_generated_profile(self):
|
||||
for profile in ('', 'acoustic', 'electric'):
|
||||
requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7'), profile)
|
||||
requirements = generator.fretboard.chord_requirements(generator.harmony.parse_symbol('C7'), respect_bass=profile != 'electric')
|
||||
for classes in ({0, 4, 10}, {4, 7, 10}, {0, 4, 7, 10}):
|
||||
self.assertTrue(requirements.covers(classes))
|
||||
self.assertFalse(requirements.covers({4, 10}))
|
||||
candidates = generator.guitar.chord_positions(requirements, prune=False, profile=profile)
|
||||
candidates = generator.acoustic_voicing.candidates(requirements) if profile == 'acoustic' else generator.guitar.chord_positions(requirements, prune=False, profile=profile)
|
||||
self.assertTrue(candidates)
|
||||
self.assertTrue(all({p.midi % 12 for p in shape} & {0, 7} for shape in candidates))
|
||||
self.assertEqual({frozenset({p.midi % 12 for p in shape} & {0, 7}) for shape in candidates}, {frozenset({0}), frozenset({7}), frozenset({0, 7})})
|
||||
def test_altered_and_removed_fifths_keep_their_semantics(self):
|
||||
for symbol, fifth in (('Cdim7', 6), ('Caug7', 8)):
|
||||
requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol), 'electric')
|
||||
requirements = generator.fretboard.chord_requirements(generator.harmony.parse_symbol(symbol), respect_bass=False)
|
||||
self.assertEqual(requirements.foundation, (0, fifth))
|
||||
self.assertIn(fifth, requirements.required)
|
||||
self.assertNotIn(7, requirements.allowed)
|
||||
@@ -105,17 +113,19 @@ class GuitarTests(unittest.TestCase):
|
||||
chord = generator.harmony.parse_symbol('C7')
|
||||
chord['degrees'].append({'value': omitted, 'alter': 0, 'type': 'subtract'})
|
||||
for profile in ('acoustic', 'electric'):
|
||||
requirements = generator.guitar.chord_requirements(chord, profile)
|
||||
requirements = generator.fretboard.chord_requirements(chord, respect_bass=profile != 'electric')
|
||||
self.assertEqual(requirements.foundation, (remaining,))
|
||||
self.assertTrue(all(remaining in {p.midi % 12 for p in c} for c in generator.guitar.chord_positions(requirements, profile=profile)))
|
||||
candidates = generator.acoustic_voicing.candidates(requirements) if profile == 'acoustic' else generator.guitar.chord_positions(requirements, profile=profile)
|
||||
self.assertTrue(candidates)
|
||||
self.assertTrue(all(remaining in {p.midi % 12 for p in c} for c in candidates))
|
||||
chord['degrees'].append({'value': 1, 'alter': 0, 'type': 'subtract'})
|
||||
with self.assertRaisesRegex(ValueError, 'root or fifth'):
|
||||
generator.guitar.chord_requirements(chord)
|
||||
generator.fretboard.chord_requirements(chord)
|
||||
def test_explicit_degree_chord_uses_the_same_root_or_fifth_rule(self):
|
||||
standard = generator.harmony.parse_symbol('C7')
|
||||
explicit = {**standard, 'kind': 'other', 'degrees': [{'value': d, 'alter': a, 'type': 'add'} for d, a in generator.harmony.degrees(standard).items()]}
|
||||
for profile in ('', 'acoustic', 'electric'):
|
||||
self.assertEqual(generator.guitar.chord_requirements(standard, profile), generator.guitar.chord_requirements(explicit, profile))
|
||||
self.assertEqual(generator.fretboard.chord_requirements(standard, respect_bass=profile != 'electric'), generator.fretboard.chord_requirements(explicit, respect_bass=profile != 'electric'))
|
||||
def test_melody_placement_preserves_notes_timing_and_sound(self):
|
||||
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', 2, 'half', octave=4) + tests.test_musicxml.xml_note('F', 2, 'half', octave=4)))
|
||||
before = copy.deepcopy(source)
|
||||
@@ -185,7 +195,7 @@ class GuitarTests(unittest.TestCase):
|
||||
randomizer = random.Random(17)
|
||||
states = {}
|
||||
for index in range(40):
|
||||
positions = tuple(generator.guitar.TabPosition(s, randomizer.randrange(21), 0) for s in randomizer.sample(range(1, 7), 3))
|
||||
positions = tuple(generator.fretboard.TabPosition(s, randomizer.randrange(21), 0) for s in randomizer.sample(range(1, 7), 3))
|
||||
states[index] = generator.guitar.Placement(generator.guitar.VoicingCost(*(randomizer.randrange(8) for _ in generator.guitar.VoicingCost._fields)), tuple((str(i), p) for i, p in enumerate(positions)), None, ())
|
||||
grid = generator.guitar.nearest_positions(states)
|
||||
for x, row in enumerate(grid):
|
||||
|
||||
Reference in New Issue
Block a user