import math MODEL_RATE = 88200 LOOKAHEAD_SECONDS = .030 def register_group(written): return 0 if written <= 73 else 1 if written <= 75 else 2 if written == 76 else 3 def prepare_controls(events, prepare_gap=False, prepare_pressure=False, lookahead=LOOKAHEAD_SECONDS): if not .018 + 1 / MODEL_RATE < lookahead < .060: raise ValueError('Preparation must precede the unchanged tongue anticipation window') result, preparations = [], [] for index, event in enumerate(events): pressure = event['pressurePa'] if event.get('pressureEndPa', pressure) != pressure: raise ValueError('This controller preserves constant per-note controls, not an unsupported pressure ramp') current = dict(event, originalNoteIndex=index, preparationOnly=False) if index + 1 == len(events) or register_group(event['writtenMidi']) == register_group(events[index + 1]['writtenMidi']): result.append(current) continue following = events[index + 1] start = round(event['time'] * MODEL_RATE) end = round((event['time'] + event['duration']) * MODEL_RATE) next_start = round(following['time'] * MODEL_RATE) prepared = round((following['time'] - lookahead) * MODEL_RATE) if not start + math.ceil(.008 * MODEL_RATE) < prepared < end or prepared >= next_start - math.ceil(.018 * MODEL_RATE): result.append(current) continue prepared_time = prepared / MODEL_RATE current['duration'] = prepared_time - event['time'] tail = dict(event, originalNoteIndex=index, preparationOnly=True, time=prepared_time, duration=(end - prepared) / MODEL_RATE, tongued=False) tail['pressurePa'] = following['pressurePa'] if prepare_pressure else pressure tail['pressureEndPa'] = tail['pressurePa'] tail['gapM'] = following['gapM'] if prepare_gap else event['gapM'] result.extend((current, tail)) preparations.append({'originalNoteIndex': index, 'nextOriginalNoteIndex': index + 1, 'heldWrittenMidi': event['writtenMidi'], 'followingWrittenMidi': following['writtenMidi'], 'preparationStartModelSample': prepared, 'originalGateEndModelSample': end, 'followingMusicalOnsetModelSample': next_start, 'lookaheadSeconds': lookahead, 'pressurePrepared': prepare_pressure, 'gapPrepared': prepare_gap, 'preparedPressurePa': tail['pressurePa'], 'preparedGapM': tail['gapM'], 'noNewTonguedAttackOrFingeringChange': True}) return result, preparations