Solo
ci / checks (push) Failing after 23m43s

This commit is contained in:
khodowany
2026-10-02 06:33:01 -05:00
parent a0bd19b3ba
commit 5fff5a165e
13 changed files with 786 additions and 211 deletions
+3 -1
View File
@@ -58,7 +58,9 @@ add_test(
COMMAND ${VAMP_PYTHON_COMMAND} -m unittest discover -s tests -p test_*.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
set_tests_properties(product_vamp_generator PROPERTIES TIMEOUT 180)
# Complete deterministic phrase searches make this suite CPU-sensitive;
# leave headroom for concurrent asset generation on development machines.
set_tests_properties(product_vamp_generator PROPERTIES TIMEOUT 300)
add_custom_target(check
COMMAND ${CMAKE_CTEST_COMMAND}
--test-dir ${CMAKE_BINARY_DIR}
+133
View File
@@ -0,0 +1,133 @@
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('')
self.assertEqual(phrasing.evaluate(whole,incoming,harmony,F(),F(4),F(4)),
phrasing.evaluate(split,incoming,harmony,F(),F(4),F(4)))
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<b<=12 for a,b in pattern))
self.assertTrue(all(b<=c for (a,b),(c,d) in zip(pattern,pattern[1:])))
def test_two_bar_retrieval_uses_the_richer_one_bar_vocabulary(self):
values=themes.rhythm_windows(tests.rhythm_fixtures.library(),'4/4',F(8),realization.SearchContext())
self.assertTrue(any(b-a==F(1,2) for pattern,_ in values for a,b in pattern))
def test_endpoint_ranking_distinguishes_major_seventh_from_dominant(self):
major=harmonic.Harmony(choices(('CM7',)*4))
dominant=harmonic.Harmony(choices(('C7',)*4))
self.assertLess(phrasing.endpoint_cost(F(16),F(),F(128),0,major,F(4)),
phrasing.endpoint_cost(F(16),F(),F(128),0,dominant,F(4)))
def test_protected_tail_is_sounding_time_not_an_immutable_whole_event(self):
original=(note(0,4,'e',event_id='arrival'),)
tail=types.SustainedTail(F(2),F(4),64)
draft=types.MelodicDraft('held',F(),F(4),original,(),(),(),(),None,'close',sustained_tails=(tail,))
revised=(note(0,1,'g',event_id='a'),note(1,2,'f',event_id='b'),note(2,4,'e',event_id='c'))
self.assertTrue(drafts.contract_valid(draft,revised)[0])
tied=(*revised[:-1],note(2,3,'e',event_id='c'),note(3,4,'e',event_id='c',continuation=True))
self.assertTrue(drafts.contract_valid(draft,tied)[0])
repeated=(*revised[:-1],note(2,3,'e',event_id='c'),note(3,4,'e',event_id='d'))
self.assertFalse(drafts.contract_valid(draft,repeated)[0])
def test_planned_endings_have_varied_timing_and_full_chord_permission(self):
harmony=harmonic.Harmony(choices(('Gm7','C7','FM7','BbM7')))
source=types.SourcePhrase('phrase',F(),F(16),(note(0,1,'d'),),(),'')
options=phrasing.endings(source,types.ContinuationState(''), 'acoustic',harmony,F(4))
self.assertTrue(options)
self.assertGreater(len({(a.earliest,a.duration) for a in options}),1)
for anchor in options:
self.assertGreaterEqual(anchor.duration,2)
self.assertLessEqual(anchor.duration,4)
self.assertIn(anchor.pitch.number()%12,harmony.members(anchor.earliest,anchor.hold_until))
self.assertTrue(harmony.evaluate((types.Note(anchor.earliest,anchor.hold_until,anchor.pitch),),complete=True).valid)
def test_rhythmic_development_keeps_a_heard_turn_without_copying_its_timing(self):
original=melody(((0,1,'d'),(1,2,'f'),(2,3,'e')))
home=types.RealizedMotif('home','seed',original,F(3),'introduce','contour')
ref=themes.home_reference(home,'develop_contour')
context=realization.SearchContext()
patterns=themes.patterns(ref,F(),F(8),tests.rhythm_fixtures.library(),'4/4',context,home=home,operation='develop_contour')
self.assertGreater(len({p[:3] for p,_ in patterns}),1)
actual=(note(0,F(1,2),'d',event_id='0'),note(F(1,2),2,'f',event_id='1'),note(2,3,'e',event_id='2'))
feature=themes.feature(ref,actual,treatment='develop_contour')
from generator.solo.references import feature_error
self.assertEqual(feature_error(feature,actual),0)
def test_repetition_remembers_recent_figures_but_not_tie_splits(self):
original=melody(((0,1,'d'),(1,2,'f'),(2,3,'e'),(3,4,'f')))
old=phrasing.heard(original,'introduce')
repeated=tuple(dataclasses.replace(n,start=n.start+8,end=n.end+8) for n in original)
figure=phrasing.heard(repeated,'develop_contour')
self.assertGreater(phrasing.repetition(figure,(old,),F(4)),phrasing.repetition(figure,(),F(4)))
returned=dataclasses.replace(figure,operation='return')
self.assertGreater(phrasing.repetition(returned,(old,old,old),F(4)),phrasing.repetition(returned,(old,),F(4)))
hold=(note(0,4,'e'),)
split=(note(0,2,'e'),note(2,4,'e',continuation=True))
self.assertEqual(phrasing.heard(hold,'close'),phrasing.heard(split,'close'))
self.assertEqual(phrasing.repetition(phrasing.heard(hold,'close'),(),F(4)),0)
def test_high_electric_state_proposes_a_lower_goal(self):
high=melody(((0,1,'c'),(1,2,'e'),(2,3,'d')))
high=tuple(dataclasses.replace(n,pitch=dataclasses.replace(n.pitch,octave=5)) for n in high)
state=types.ContinuationState('',last=high[-1],history=(phrasing.heard(high,'develop_contour'),),register_peak=76)
self.assertLess(phrasing.register_goal(state,'electric',74),min(n.pitch.number() for n in high))
def test_internal_short_notes_can_be_elaborated_without_consuming_arrival(self):
notes=melody(((0,1,'g'),(1,2,'f'),(2,3,'g'),(3,5,'e')))
source=types.SourcePhrase('internal',F(),F(5),notes,(),'')
harmony=harmonic.Harmony(choices(('C7','C7')))
tails=(types.SustainedTail(F(3),F(5),64),)
options=leads.elaborate(notes,(),harmony,tests.solo_fixtures.library(),tests.rhythm_fixtures.library(),'4/4',source,sustain_entry=True,tails=tails)
self.assertTrue(options)
for actual,_,_ in options:
self.assertTrue(any(n.pitch.number()==64 and n.start<=3 and n.end>=5 for n in actual))
self.assertTrue(harmony.evaluate(actual,complete=True).valid)
if __name__=='__main__':unittest.main()
+14 -3
View File
@@ -93,10 +93,10 @@ class ThemeProduction(unittest.TestCase):
for phrase in self.solo.phrases:
self.assertTrue(drafts.contract_valid(phrase.draft,composer.phrase_notes(phrase))[0])
self.assertFalse(similarity.near_copy(phrase.refined.notes,themes.source_notes(self.analysis),phrase.plan.start,phrase.plan.end)['near_copy'])
self.assertEqual(self.solo.phrases[0].plan.closure,'open')
self.assertTrue(all(a.role=='intermediate' for a in self.solo.phrases[0].anchors))
self.assertEqual(self.solo.phrases[0].plan.closure,'closed')
self.assertTrue(all(a.duration>=2 for a in self.solo.phrases[0].anchors))
self.assertEqual(self.solo.phrases[-1].anchors[-1].role,'terminal')
self.assertTrue(any(t.operation=='repeat_with_harmonic_adaptation' for p in self.solo.phrases for t in p.draft.transformations))
self.assertTrue(any(t.operation in ('develop_contour','develop_rhythm','continue','return') for p in self.solo.phrases for t in p.draft.transformations))
def test_library_routes_match_the_actual_complete_theme(self):
self.assertTrue(any(p.routes for p in self.solo.phrases))
@@ -108,6 +108,17 @@ class ThemeProduction(unittest.TestCase):
self.assertEqual((note.start,note.end,note.pitch),(actual.start,actual.end,actual.pitch))
self.assertEqual(route.notes[-1].pitch,route.destination.pitch)
def test_report_can_compare_a_source_hold_cut_by_new_phrase_boundaries(self):
from generator.solo import report
phrases=[]
for i,p in enumerate(self.solo.phrases):
held=notes_fixture.note(p.plan.start,p.plan.end,'c',event_id='source_hold',continuation=i>0)
phrases.append(dataclasses.replace(p,initial=dataclasses.replace(p.initial,notes=(held,))))
solo=dataclasses.replace(self.solo,phrases=tuple(phrases))
detail=report.structure(solo)
self.assertEqual(len(detail['phrases']),len(phrases))
self.assertTrue(phrases[-1].initial.notes[0].continuation)
def test_incoming_pitch_changes_concrete_continuation_pool(self):
harmony=harmonic.Harmony(self.solo.choices)
home=self.solo.phrases[0].state.home