Source code for pico_synth_sandbox.arpeggiator

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

import random
from pico_synth_sandbox import clamp
from pico_synth_sandbox.timer import Timer
from pico_synth_sandbox.keyboard import Note

[docs]class Arpeggiator(Timer): NUM_MODES = 6 MODE_UP = 0 MODE_DOWN = 1 MODE_UPDOWN = 2 MODE_DOWNUP = 3 MODE_PLAYED = 4 MODE_RANDOM = 5 def __init__(self, bpm=120, steps=2.0, mode=0, octaves=0, probability=1.0): Timer.__init__(self, bpm=bpm, steps=steps, gate=0.3 ) self._raw_notes = [] self._notes = [] self.set_mode(mode) self.set_octaves(octaves) self._probability = probability self._keyboard = None def _reset(self, immediate=True): Timer._reset(self, immediate) self._pos = 0 def get_octaves(self): return self._octaves def set_octaves(self, value): self._octaves = int(value) if self._notes: self.update_notes(self._raw_notes) def get_probability(self): return self._probability def set_probability(self, value): self._probability = clamp(value, 0.0, 1.0) def set_keyboard(self, keyboard): self._keyboard = keyboard def _enable(self): if self._keyboard: self.update_notes(self._keyboard.get_notes()) def _disable(self): if self._keyboard: self._keyboard.force_update() def get_mode(self): return self._mode def set_mode(self, value): self._mode = value % self.NUM_MODES if self._notes: self.update_notes(self._raw_notes) def _get_notes(self, notes=[]): if not notes: return notes if abs(self._octaves) > 0: l = len(notes) for octave in range(1,abs(self._octaves)+1): if self._octaves < 0: octave = octave * -1 for i in range(0,l): notes.append(Note(notes[i].notenum + octave*12, notes[i].velocity)) if self._mode == self.MODE_UP: notes.sort() elif self._mode == self.MODE_DOWN: notes.sort(reverse=True) elif self._mode == self.MODE_UPDOWN: notes.sort() if len(notes) > 2: _notes = notes[1:-1].copy() _notes.reverse() notes = notes + _notes elif self._mode == self.MODE_DOWNUP: notes.sort(reverse=True) if len(notes) > 2: _notes = notes[1:-1].copy() _notes.reverse() notes = notes + _notes # MODE_PLAYED = notes stay as is, MODE_RANDOM = index is randomized on update return notes def update_notes(self, notes=[]): if not self._notes: self._reset() self._raw_notes = notes.copy() self._notes = self._get_notes(notes) def _update(self): if self._notes: if self._probability < 1.0 and (self._probability == 0.0 or random.random() > self._probability): return if self.get_mode() == self.MODE_RANDOM: self._pos = random.randrange(0,len(self._notes),1) else: self._pos = (self._pos+1) % len(self._notes) self._do_press(self._notes[self._pos].notenum, self._notes[self._pos].velocity)