34 lines
1.8 KiB
Python
34 lines
1.8 KiB
Python
"""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
|
|
|
|
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-a<F(1,2) for a,b in pattern):continue
|
|
descriptor=(len(pattern),pattern[0][0]>0,any(a[1]<b[0] for a,b in zip(pattern,pattern[1:])),len(set(b-a for a,b in pattern)))
|
|
representatives.setdefault(descriptor,(item.id,pattern,bar.length,('develop','answer')))
|
|
self.catalog[meter]=tuple(representatives.values())
|
|
self.cache={}
|