Spaces:
Running
on
Zero
Running
on
Zero
from typing import List | |
class KeyRange: | |
def __init__(self, root_key: int = 60, low_key: int = None, high_key: int = None): | |
self.root_key = root_key | |
self.low_key = low_key | |
self.high_key = high_key | |
if self.low_key is None: | |
self.low_key = self.root_key | |
if self.high_key is None: | |
self.high_key = self.root_key | |
def in_range(self, key): | |
return self.low_key <= key <= self.high_key | |
def __eq__(self, other): | |
return self.low_key == other.low_key and self.root_key == other.root_key and self.high_key == other.high_key | |
class VelocityRange: | |
def __init__(self, low_velocity: int = 0, high_velocity: int = 127): | |
self.low_velocity = low_velocity | |
self.high_velocity = high_velocity | |
def in_range(self, key): | |
return self.low_velocity <= key <= self.high_velocity | |
def __eq__(self, other): | |
return self.low_velocity == other.low_velocity and self.high_velocity == other.root_key and\ | |
self.high_velocity == other.high_key | |
class Sample: | |
def __init__(self, filename: str, root_key: int = 60, low_key: int = None, high_key: int = None): | |
self.velocity_range = VelocityRange() | |
self.filename = filename | |
self.key_range = KeyRange(root_key, low_key, high_key) | |
self.index = 0 | |
def __repr__(self) -> str: | |
return 'Sample(filename=' + self.filename + ', root_key=' + str(self.key_range.root_key) + ', low_key=' + str( | |
self.key_range.low_key) + ', high_key=' + str(self.key_range.high_key) + ')' | |
def __eq__(self, other): | |
return self.filename == other.filename and self.key_range == other.key_range and self.index == other.index | |
class SoundFont: | |
def __init__(self, samples: List[Sample], loop_mode: str = "no_loop", polyphony: str = None, release: int = None, | |
instrument_name=None): | |
self.instrument_name = instrument_name | |
self.samples = samples | |
self.loop_mode = loop_mode | |
self.polyphony = polyphony | |
self.release = release | |
def root_keys(self): | |
return sorted(set([sample.key_range.root_key for sample in self.samples])) | |
def range_for_key(self, key) -> KeyRange: | |
samples_in_range = [sample for sample in self.samples if sample.key_range.in_range(key)] | |
return samples_in_range[0].key_range if len(samples_in_range) > 0 else None | |
def samples_for_root_key(self, root_key): | |
return [sample for sample in self.samples if sample.key_range.root_key == root_key] | |
def set_range(self, root_key, low_key=None, high_key=None): | |
for sample in self.samples_for_root_key(root_key): | |
if low_key is not None: | |
sample.key_range.low_key = low_key | |
if high_key is not None: | |
sample.key_range.high_key = high_key | |
class HighLowKeyDistributor: | |
def distribute(self, soundfont: SoundFont, low_key: int = 21, high_key: int = 108): | |
soundfont.samples.sort(key=lambda sample: sample.key_range.root_key, reverse=False) | |
prev_root_key: int = None | |
for root_key in soundfont.root_keys(): | |
range = soundfont.range_for_key(root_key) | |
if prev_root_key is None: | |
lo_key = min(low_key, range.low_key) | |
soundfont.set_range(root_key, low_key=min(low_key, lo_key)) | |
else: | |
prev_range = soundfont.range_for_key(prev_root_key) | |
mid_sample_key = int((range.low_key - prev_range.high_key) / 2) + prev_range.high_key | |
soundfont.set_range(prev_root_key, high_key=mid_sample_key) | |
soundfont.set_range(root_key, low_key=mid_sample_key + 1) | |
prev_root_key = root_key | |
soundfont.set_range(soundfont.root_keys()[-1], | |
high_key=max(high_key, soundfont.range_for_key(soundfont.root_keys()[-1]).high_key)) | |
class NoteNameMidiNumberMapper: | |
def __init__(self): | |
self.index_offset = 21 | |
self.note_name_midi_number_map: List[str] = [] | |
for octave_number in range(0, 8): | |
for c in range(ord('A'), ord('B') + 1): | |
self._add_note(c, octave_number) | |
for c in range(ord('C'), ord('G') + 1): | |
self._add_note(c, octave_number + 1) | |
self.note_name_midi_number_map.append("C8") | |
def _add_note(self, c, octave_number): | |
self.note_name_midi_number_map.append(f"{chr(c)}{str(octave_number)}") | |
if chr(c) != "A" and chr(c) != "E": | |
self.note_name_midi_number_map.append(f"{chr(c)}#{str(octave_number)}") | |
def mapped_sample(self, filename: str): | |
for note in self.note_name_midi_number_map: | |
if note in filename: | |
return Sample(filename, self.map(note)) | |
return Sample(filename) | |
def map(self, note_name: str): | |
return self.note_name_midi_number_map.index(note_name) + self.index_offset | |