Source code for pico_synth_sandbox.voice.oscillator

# pico_synth_sandbox/voice/oscillator.py
# 2023 Cooper Dalrymple - me@dcdalrymple.com
# GPL v3 License

from pico_synth_sandbox import LOG_2, clamp
from pico_synth_sandbox.voice import Voice, AREnvelope, LerpBlockInput
import math
import synthio

[docs]class Oscillator(Voice): def __init__(self, root=440.0): Voice.__init__(self) self._filter_envelope = AREnvelope(amount=0.0) self._filter_lfo = synthio.LFO( waveform=None, rate=1.0, scale=0.0, offset=0.0 ) self._root = root self.coarse_tune = 0.0 self.fine_tune = 0.0 self.bend_amount = 0.0 self.bend = 0.0 self._attack_time = 0.0 self._decay_time = 0.0 self._release_time = 0.0 self._attack_level = 1.0 self._sustain_level = 0.75 self._freq_lerp = LerpBlockInput() self._pitch_lerp = LerpBlockInput() self._note = synthio.Note( waveform=None, frequency=self._root, amplitude=synthio.LFO( # Tremolo waveform=None, rate=1.0, scale=0.0, offset=1.0 ), bend=synthio.Math( synthio.MathOperation.SUM, self._freq_lerp.get(), # Frequency Lerp synthio.LFO( # Vibrato waveform=None, rate=1.0, scale=0.0, offset=0.0 ), self._pitch_lerp.get() # Pitch Bend Lerp ), panning=synthio.LFO( waveform=None, rate=1.0, scale=0.0, offset=0.0 ) )
[docs] def get_notes(self): return [self._note]
[docs] def get_blocks(self): return self._filter_envelope.get_blocks() + self._freq_lerp.get_blocks() + self._pitch_lerp.get_blocks() + [ self._filter_lfo, self._note.amplitude, self._note.bend, self._note.panning, ]
[docs] def press(self, notenum, velocity=1.0): if not Voice.press(self, notenum, velocity): return False frequency = synthio.midi_to_hz(notenum) self.set_frequency(frequency) self._filter_envelope.press() return True
[docs] def release(self): if not Voice.release(self): return False self._filter_envelope.release() return True
def set_frequency(self, value): self._freq_lerp.set(math.log(value/self._root)/LOG_2) def set_glide(self, value): self._freq_lerp.set_rate(value) def set_pitch_bend_amount(self, value): self.bend_amount = value self._update_pitch_bend() def set_pitch_bend(self, value): self.bend = value self._update_pitch_bend() def _update_pitch_bend(self): self._pitch_lerp.set(self.bend * self.bend_amount) def set_coarse_tune(self, value): self.coarse_tune = value self._update_root() def set_fine_tune(self, value): self.fine_tune = value self._update_root() def _update_root(self): self._note.frequency = self._root * pow(2,self.coarse_tune) * pow(2,self.fine_tune) def set_waveform(self, waveform): self._note.waveform = waveform def set_loop(self, start=0.0, end=1.0): if self._note.waveform is None or len(self._note.waveform) < 2: return start = clamp(start, 0.0, 1.0) end = clamp(end, start, 1.0) waveform_length = len(self._note.waveform) start = round(start * (waveform_length-2)) end = clamp(round(end * (waveform_length-1) + 1), start+2, waveform_length) self._note.waveform_loop_start = start self._note.waveform_loop_end = end
[docs] def set_level(self, value): self._note.amplitude.offset = value
def set_tremolo_rate(self, value): self._note.amplitude.rate = value def set_tremolo_depth(self, value): self._note.amplitude.scale = value def set_vibrato_rate(self, value): self._note.bend.b.rate = value def set_vibrato_depth(self, value): self._note.bend.b.scale = value def set_pan_rate(self, value): self._note.panning.rate = value def set_pan_depth(self, value): self._note.panning.scale = value def set_pan(self, value): self._note.panning.offset = value # Envelope def _update_envelope(self): mod = self._get_velocity_mod() self._note.envelope = synthio.Envelope( attack_time=self._attack_time, decay_time=self._decay_time, release_time=self._release_time, attack_level=mod*self._attack_level, sustain_level=mod*self._sustain_level ) def set_envelope_attack_time(self, value, update=True): self._attack_time = value if update: self._update_envelope() def set_envelope_decay_time(self, value, update=True): self._decay_time = value if update: self._update_envelope() def set_envelope_release_time(self, value, update=True): self._release_time = value if update: self._update_envelope() def set_envelope_attack_level(self, value, update=True): self._attack_level = value if update: self._update_envelope() def set_envelope_sustain_level(self, value, update=True): self._sustain_level = value if update: self._update_envelope() def set_envelope(self, velocity_amount=1.0, attack_time=0.0, decay_time=0.0, release_time=0.0, attack_level=1.0, sustain_level=0.75, update=True): self.set_velocity_amount(velocity_amount) self.set_envelope_attack_time(attack_time, False) self.set_envelope_decay_time(decay_time, False) self.set_envelope_release_time(release_time, False) self.set_envelope_attack_level(attack_level, False) self.set_envelope_sustain_level(sustain_level, False) if update: self._update_envelope() # Filter def _get_filter_frequency_value(self): return self._filter_frequency + self._filter_envelope.get_value() + self._filter_lfo.value def set_filter_envelope_attack_time(self, value): self._filter_envelope.set_attack(value) def set_filter_envelope_release_time(self, value): self._filter_envelope.set_release(value) def set_filter_envelope_amount(self, value): self._filter_envelope.set_amount(value) def set_filter_lfo_rate(self, value): self._filter_lfo.rate = value def set_filter_lfo_depth(self, value): self._filter_lfo.scale = value
[docs] def set_filter(self, type=0, frequency=1.0, resonance=0.0, envelope_attack_time=0.0, envelope_release_time=0.0, envelope_amount=0.0, lfo_rate=1.0, lfo_depth=0.0, synth=None, update=True): Voice.set_filter(self, type, frequency, resonance, None, False) self.set_filter_envelope_attack_time(envelope_attack_time) self.set_filter_envelope_release_time(envelope_release_time) self.set_filter_envelope_amount(envelope_amount) self.set_filter_lfo_rate(lfo_rate) self.set_filter_lfo_depth(lfo_depth) if update and not synth is None: self._update_filter(synth)