-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwavetable.py
41 lines (37 loc) · 1.68 KB
/
wavetable.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4_oscillators_waveforms/wavetable.py
# simple wavetable class for synthio
# part of todbot circuitpython synthio tutorial
# 10 Feb 2025 - @todbot / Tod Kurt
#
import ulab.numpy as np
import synthio
import adafruit_wave
class Wavetable:
""" A 'waveform' for synthio.Note uses a WAV containing a wavetable
and provides a scannable wave position."""
def __init__(self, filepath, wave_len=256):
self.w = adafruit_wave.open(filepath)
self.wave_len = wave_len # how many samples in each wave
if self.w.getsampwidth() != 2 or self.w.getnchannels() != 1:
raise ValueError("unsupported WAV format")
# empty buffer we'll copy into
self.waveform = np.zeros(wave_len, dtype=np.int16)
self.num_waves = self.w.getnframes() // self.wave_len
self._wave_pos = 0
@property
def wave_pos(self): return self._wave_pos
@wave_pos.setter
def wave_pos(self, pos):
"""Pick where in wavetable to be, morphing between waves"""
pos = min(max(pos, 0), self.num_waves-1) # constrain
samp_pos = int(pos) * self.wave_len # get sample position
self.w.setpos(samp_pos)
waveA = np.frombuffer(self.w.readframes(self.wave_len), dtype=np.int16)
self.w.setpos(samp_pos + self.wave_len) # one wave up
waveB = np.frombuffer(self.w.readframes(self.wave_len), dtype=np.int16)
pos_frac = pos - int(pos) # fractional position between wave A & B
# mix waveforms A & B
self.waveform[:] = Wavetable.lerp(waveA, waveB, pos_frac)
self._wave_pos = pos
# mix between values a and b, works with numpy arrays too, t ranges 0-1
def lerp(a, b, t): return (1-t)*a + t*b