import dataclasses from fractions import Fraction as F import unittest from generator.solo import drafts, harmonic, phrasing, themes, types, leads, realization from tests.test_solo_development import note from tests.test_solo_passages import choices from tests.test_solo_themes import melody import tests.rhythm_fixtures import tests.solo_fixtures class SustainedPhrasing(unittest.TestCase): def test_vocal_cutoff_does_not_strand_a_one_beat_final_phrase(self): from generator.solo import analysis from tests.test_solo import song document=song(count=32,chords=('CM7',)) source=analysis.analyze(document) harmony=harmonic.Harmony(choices(('CM7',)*32)) values=phrasing.window_options(source,document['staves'][0]['measures'],F(100),F(125),harmony,types.ContinuationState('',closures=5),limit=4) self.assertEqual(values[0].end,125) self.assertFalse(any(0<125-v.end<4 for v in values)) def test_production_cost_uses_complete_sounding_events_across_cells(self): harmony=harmonic.Harmony(choices(('C7','C7'))) whole=(note(0,1,'g'),note(1,F(3,2),'f'),note(F(3,2),4,'e')) split=(whole[0],whole[1],dataclasses.replace(whole[2],end=2),note(2,4,'e',continuation=True)) incoming=types.ContinuationState('') from tests.test_composer_repair import evaluated self.assertEqual(evaluated(whole,harmony),evaluated(split,harmony)) def test_route_cache_preserves_concrete_provenance_and_uncached_selection(self): from generator.solo import routes,sequences,policy harmony=harmonic.Harmony(choices(('C7',))) notes=melody(((0,1,'g'),(1,2,'f'),(2,3,'e'))) library=tests.solo_fixtures.library() raw=routes._candidates(notes,{'2'},harmony,library,(41,76)) expected=sequences.diverse(raw, lambda r:(sum(abs(b.pitch.number()-a.pitch.number()) for a,b in zip(r.notes,r.notes[1:])),r.id), lambda r:sequences.family(tuple(b.pitch.number()-a.pitch.number() for a,b in zip(r.notes,r.notes[1:]))),policy.DEFAULT.join_alternatives) cached=routes.candidates(notes,{'2'},harmony,library,(41,76)) self.assertEqual(tuple((r.id,r.notes) for r in expected),tuple((r.id,r.notes) for r in cached)) renamed=tuple(dataclasses.replace(n,event_id='other'+n.event_id,sources=('other-source',)) for n in notes) changed=routes.candidates(renamed,{'other2'},harmony,library,(41,76)) self.assertEqual(len(harmony.route_cache),1) self.assertTrue(all(n.sources==('other-source',) for r in changed for n in r.notes)) self.assertTrue(all(n.event_id.startswith('other') for r in changed for n in r.notes)) def test_extended_closing_unit_has_complete_timing_beyond_two_bars(self): values=themes.rhythm_windows(tests.rhythm_fixtures.library(),'4/4',F(12),realization.SearchContext()) self.assertTrue(values) self.assertTrue(any(any(a>=8 for a,b in pattern) for pattern,_ in values)) for pattern,_ in values: self.assertTrue(all(0<=a=5 for n in actual)) self.assertTrue(harmony.evaluate(actual,complete=True).valid) if __name__=='__main__':unittest.main()