"""Complete generated rhythm groups, indexed once, independent of source rhythms.""" from fractions import Fraction as F from generator import vocabulary from generator.solo import groups, policy class Rhythms: def __init__(self, library, tempo): self.groups=[]; limits=vocabulary.data('roles')['difficulty'] for item in vocabulary.data('solo')['groups']: difficulty=item.get('difficulty') if difficulty and tempo>limits[difficulty+'_max_bpm']:continue cursor=F();pattern=[] for value in item['values']: duration=F(value.removeprefix('r:')) if not value.startswith('r:'):pattern.append((cursor,cursor+duration)) cursor+=duration self.groups.append((item['id'],tuple(pattern),cursor,tuple(item['purpose']))) for cell in vocabulary.rhythm_cells(tempo): # Imported authored cells remain whole. They are never sliced to fit. if groups.valid(cell.pattern):self.groups.append((cell.id,cell.pattern,cell.duration,('develop','answer') if cell.purpose=='answer' else ('arpeggio','scalar','develop','neighbor'))) # Meter-indexed catalog representatives: file counts do not set style frequency. self.catalog={} for meter,items in library.snippets.items(): representatives={} for item in items: for bar in item.bars: pattern=tuple((e.onset,e.onset+e.duration) for e in bar.events if e.sounding) if any(e.continuation for e in bar.events) or not pattern or not groups.valid(pattern):continue if any(b-a0,any(a[1]=1] if home is not None and uses<2 and home in available: selected=home else: # Establish one rhythmic idea before reshaping it. A complete phrase # is not a fresh unrelated catalog draw at every short boundary. selected=min(large or available,key=lambda g:policy.stable_rank(seed,('rhythm',len(chosen)//4,g[0]))) home=selected;uses=0 uses+=1 name,pattern,length,_=selected events=tuple((cursor+a,cursor+b) for a,b in pattern) notes.extend(events);sources.append(name) chosen.extend(groups.describe(pattern,origin=cursor,source=name)) cursor+=length result=(tuple(notes),tuple(chosen),tuple(sources));self.cache[key]=result return result