Rhythm Patterns
This commit is contained in:
+14
-7
@@ -23,14 +23,17 @@ def _load(contents):
|
||||
result = {}
|
||||
for name, text in contents:
|
||||
value = json.loads(text)
|
||||
if value.get('version') != (4 if name == 'accompaniment' else 1) or not value.get('provenance') or not value.get('license'):
|
||||
if value.get('version') != (5 if name == 'accompaniment' else 1) or not value.get('provenance') or not value.get('license'):
|
||||
raise ValueError(f'{name}: unsupported or unprovenanced production vocabulary')
|
||||
if name == 'accompaniment':
|
||||
signatures = {}
|
||||
for part in ('electric', 'acoustic'):
|
||||
policy = value['instruments'][part]
|
||||
if part == 'acoustic' and policy.get('first_attack') != 'bass_or_combined':
|
||||
raise ValueError('acoustic patterns require a bass-or-combined first attack policy')
|
||||
if part == 'acoustic' and policy.get('first_attack') != 'bass_per_harmony':
|
||||
raise ValueError('acoustic patterns require bass at the first attack of each harmony')
|
||||
groove = policy['groove']
|
||||
if any(type(groove[k]) is not int for k in ('cycle_patterns', 'slot_candidates', 'fill_spacing_measures')) or not 1 <= groove['cycle_patterns'] <= 4 or not 4 <= groove['slot_candidates'] <= 8 or groove['fill_spacing_measures'] < 8:
|
||||
raise ValueError('invalid bounded accompaniment groove policy')
|
||||
identifiers = set()
|
||||
signatures[part] = set()
|
||||
for pattern in policy['patterns']:
|
||||
@@ -41,7 +44,7 @@ def _load(contents):
|
||||
if not 1 <= len(lengths) <= 2 or any(n <= 0 for n in lengths):
|
||||
raise ValueError('patterns require one or two positive measure lengths')
|
||||
# Validate musical events before any measure projection or ties.
|
||||
previous = fractions.Fraction()
|
||||
previous, releases = fractions.Fraction(), {}
|
||||
onsets = []
|
||||
for row in pattern['events']:
|
||||
onset, duration = fractions.Fraction(row['onset']), fractions.Fraction(row['duration'])
|
||||
@@ -51,15 +54,19 @@ def _load(contents):
|
||||
raise ValueError('comping vocabulary uses a quarter/eighth grid')
|
||||
if part == 'acoustic':
|
||||
if row['layer'] == 'combined':
|
||||
if duration <= 1:
|
||||
raise ValueError('combined acoustic sustain must exceed one beat')
|
||||
if duration < 1 and duration != fractions.Fraction(1, 2):
|
||||
raise ValueError('combined acoustic attacks require quarter/eighth notes or sustains')
|
||||
elif row['layer'] not in ('bass', 'upper') or duration not in (fractions.Fraction(1, 2), 1):
|
||||
raise ValueError('acoustic short attacks require bass/upper quarter/eighth notes')
|
||||
elif row['layer'] != 'electric':
|
||||
raise ValueError('electric pattern must contain electric chord attacks')
|
||||
if row.get('anticipate') and row['layer'] not in ('upper', 'electric'):
|
||||
raise ValueError('only upper/electric chords can anticipate')
|
||||
previous = onset + duration
|
||||
lanes = ('bass', 'upper') if row['layer'] == 'combined' else (row['layer'],)
|
||||
if any(releases.get(lane, fractions.Fraction()) > onset for lane in lanes):
|
||||
raise ValueError('overlapping accompaniment attacks on the same strings')
|
||||
releases.update({lane: onset + duration for lane in lanes})
|
||||
previous = onset
|
||||
onsets.append(onset)
|
||||
if len(lengths) == 2:
|
||||
signatures[part].add((lengths, tuple(onsets)))
|
||||
|
||||
Reference in New Issue
Block a user