@@ -1,73 +1,69 @@
|
||||
import dataclasses, unittest
|
||||
from fractions import Fraction as F
|
||||
from generator.solo import types, harmonic, groups
|
||||
from tests.solo_fixtures import note,choices
|
||||
import fractions
|
||||
import generator.solo.types
|
||||
import generator.solo.harmonic
|
||||
import generator.solo.groups
|
||||
import tests.solo_fixtures
|
||||
import tests.test_independent_generation
|
||||
import tempfile
|
||||
import pathlib
|
||||
import generator.input_audit
|
||||
|
||||
def melody(rows): return tuple(tests.solo_fixtures.note(a, b, p, event_id=str(i)) for i, (a, b, p) in enumerate(rows))
|
||||
|
||||
def melody(rows):return tuple(note(a,b,p,event_id=str(i)) for i,(a,b,p) in enumerate(rows))
|
||||
class RhythmGroups(unittest.TestCase):
|
||||
|
||||
def test_complete_triplet_can_be_displaced_without_offsetting_binary_tail(self):
|
||||
pattern = ((F(1, 2), F(5, 6)), (F(5, 6), F(7, 6)), (F(7, 6), F(3, 2)), (F(3, 2), F(5, 2)))
|
||||
result = groups.describe(pattern)
|
||||
self.assertEqual(result[0].family, 'triplet')
|
||||
self.assertEqual(result[-1].family, 'binary')
|
||||
self.assertEqual(result[0].displacement, F(1, 2))
|
||||
|
||||
def test_partial_triplet_does_not_drift_into_binary(self):
|
||||
self.assertFalse(groups.valid(((F(), F(1, 3)), (F(1, 3), F(5, 6)))))
|
||||
self.assertFalse(groups.valid(((F(1, 3), F(4, 3)),)))
|
||||
self.assertEqual(groups.describe(((F(1, 3), F(4, 3)),), imported=True)[0].family, 'source')
|
||||
|
||||
def test_relative_rhythm_identity_does_not_count_absolute_onsets(self):
|
||||
original = melody(((0, 1, 'c'), (1, 2, 'e'), (2, 3, 'd')))
|
||||
self.assertEqual(len(set(groups.signature(original))), 1)
|
||||
moved = tuple((dataclasses.replace(n, start=n.start + 13, end=n.end + 13) for n in original))
|
||||
self.assertEqual(groups.signature(original), groups.signature(moved))
|
||||
|
||||
def test_broadening_never_cuts_inside_tuplet(self):
|
||||
pattern = ((F(), F(1, 3)), (F(1, 3), F(2, 3)), (F(2, 3), F(1)))
|
||||
self.assertEqual(groups.broaden(pattern, F(1, 2), F(4)), ())
|
||||
def test_complete_triplet_can_be_displaced_without_offsetting_binary_tail(self):
|
||||
pattern = ((fractions.Fraction(1, 2), fractions.Fraction(5, 6)), (fractions.Fraction(5, 6), fractions.Fraction(7, 6)), (fractions.Fraction(7, 6), fractions.Fraction(3, 2)), (fractions.Fraction(3, 2), fractions.Fraction(5, 2)))
|
||||
result = generator.solo.groups.describe(pattern)
|
||||
self.assertEqual(result[0].family, 'triplet')
|
||||
self.assertEqual(result[-1].family, 'binary')
|
||||
self.assertEqual(result[0].displacement, fractions.Fraction(1, 2))
|
||||
def test_partial_triplet_does_not_drift_into_binary(self):
|
||||
self.assertFalse(generator.solo.groups.valid(((fractions.Fraction(), fractions.Fraction(1, 3)), (fractions.Fraction(1, 3), fractions.Fraction(5, 6)))))
|
||||
self.assertFalse(generator.solo.groups.valid(((fractions.Fraction(1, 3), fractions.Fraction(4, 3)),)))
|
||||
self.assertEqual(generator.solo.groups.describe(((fractions.Fraction(1, 3), fractions.Fraction(4, 3)),), imported=True)[0].family, 'source')
|
||||
def test_relative_rhythm_identity_does_not_count_absolute_onsets(self):
|
||||
original = melody(((0, 1, 'c'), (1, 2, 'e'), (2, 3, 'd')))
|
||||
self.assertEqual(len(set(generator.solo.groups.signature(original))), 1)
|
||||
moved = tuple((dataclasses.replace(n, start=n.start + 13, end=n.end + 13) for n in original))
|
||||
self.assertEqual(generator.solo.groups.signature(original), generator.solo.groups.signature(moved))
|
||||
def test_broadening_never_cuts_inside_tuplet(self):
|
||||
pattern = ((fractions.Fraction(), fractions.Fraction(1, 3)), (fractions.Fraction(1, 3), fractions.Fraction(2, 3)), (fractions.Fraction(2, 3), fractions.Fraction(1)))
|
||||
self.assertEqual(generator.solo.groups.broaden(pattern, fractions.Fraction(1, 2), fractions.Fraction(4)), ())
|
||||
|
||||
class PendingTreatments(unittest.TestCase):
|
||||
|
||||
def test_complete_route_can_cross_a_search_frontier(self):
|
||||
from tests.test_independent_generation import TypedExpressionContracts
|
||||
fixture = TypedExpressionContracts()
|
||||
fixture.setUp()
|
||||
notes, contract = fixture.make()
|
||||
partial = fixture.h.evaluate(notes[:2], until=F(2), complete=False, expressions=(contract,))
|
||||
self.assertTrue(partial.valid, partial.error)
|
||||
self.assertEqual(partial.pending, (contract,))
|
||||
self.assertFalse(fixture.h.evaluate(notes[:2], until=F(3), expressions=(contract,)).valid)
|
||||
complete = fixture.h.evaluate(notes, until=F(4), complete=True, expressions=(contract,))
|
||||
self.assertTrue(complete.valid)
|
||||
self.assertFalse(complete.pending)
|
||||
def test_complete_route_can_cross_a_search_frontier(self):
|
||||
fixture = tests.test_independent_generation.TypedExpressionContracts()
|
||||
fixture.setUp()
|
||||
notes, contract = fixture.make()
|
||||
partial = fixture.h.evaluate(notes[:2], until=fractions.Fraction(2), complete=False, expressions=(contract,))
|
||||
self.assertTrue(partial.valid, partial.error)
|
||||
self.assertEqual(partial.pending, (contract,))
|
||||
self.assertFalse(fixture.h.evaluate(notes[:2], until=fractions.Fraction(3), expressions=(contract,)).valid)
|
||||
complete = fixture.h.evaluate(notes, until=fractions.Fraction(4), complete=True, expressions=(contract,))
|
||||
self.assertTrue(complete.valid)
|
||||
self.assertFalse(complete.pending)
|
||||
|
||||
class InputFirewall(unittest.TestCase):
|
||||
|
||||
def test_read_write_handles_cannot_bypass_boundary(self):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from generator.input_audit import composition
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
forbidden = Path(folder) / 'target.xml'
|
||||
forbidden.write_text('not musical input')
|
||||
for mode in ('r', 'r+'):
|
||||
with self.assertRaisesRegex(ValueError, 'Forbidden composition input'):
|
||||
with composition(Path('data'), 'electric_solo'):
|
||||
with forbidden.open(mode) as stream:
|
||||
stream.read()
|
||||
def test_read_write_handles_cannot_bypass_boundary(self):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
forbidden = pathlib.Path(folder) / 'target.xml'
|
||||
forbidden.write_text('not musical input')
|
||||
for mode in ('r', 'r+'):
|
||||
with self.assertRaisesRegex(ValueError, 'Forbidden composition input'):
|
||||
with generator.input_audit.composition(pathlib.Path('data'), 'electric_solo'):
|
||||
with forbidden.open(mode) as stream:
|
||||
stream.read()
|
||||
|
||||
class PreparedSuspensions(unittest.TestCase):
|
||||
|
||||
def test_preparation_and_delayed_resolution_are_required(self):
|
||||
h = harmonic.Harmony(choices(('Gm7', 'C7')))
|
||||
held = note(F(5, 2), F(9, 2), 'f', event_id='held')
|
||||
target = note(F(9, 2), 6, 'e', event_id='resolution')
|
||||
contract = types.ExpressionContract('prepared', 'suspension', 'prepared_step_down', (held,), target, 'C7', boundary=F(4))
|
||||
self.assertTrue(h.evaluate((held, target), expressions=(contract,)).valid)
|
||||
self.assertTrue(h.evaluate((held, target)).valid)
|
||||
self.assertFalse(h.evaluate((held, target),infer=False).valid)
|
||||
changed = dataclasses.replace(held, start=F(15, 4))
|
||||
self.assertFalse(h.evaluate((changed, target), expressions=(dataclasses.replace(contract, events=(changed,)),)).valid)
|
||||
self.assertFalse(h.evaluate((held,), expressions=(contract,)).valid)
|
||||
def test_preparation_and_delayed_resolution_are_required(self):
|
||||
h = generator.solo.harmonic.Harmony(tests.solo_fixtures.choices(('Gm7', 'C7')))
|
||||
held = tests.solo_fixtures.note(fractions.Fraction(5, 2), fractions.Fraction(9, 2), 'f', event_id='held')
|
||||
target = tests.solo_fixtures.note(fractions.Fraction(9, 2), 6, 'e', event_id='resolution')
|
||||
contract = generator.solo.types.ExpressionContract('prepared', 'suspension', 'prepared_step_down', (held,), target, 'C7', boundary=fractions.Fraction(4))
|
||||
self.assertTrue(h.evaluate((held, target), expressions=(contract,)).valid)
|
||||
self.assertTrue(h.evaluate((held, target)).valid)
|
||||
self.assertFalse(h.evaluate((held, target), infer=False).valid)
|
||||
changed = dataclasses.replace(held, start=fractions.Fraction(15, 4))
|
||||
self.assertFalse(h.evaluate((changed, target), expressions=(dataclasses.replace(contract, events=(changed,)),)).valid)
|
||||
self.assertFalse(h.evaluate((held,), expressions=(contract,)).valid)
|
||||
|
||||
Reference in New Issue
Block a user