synth
synth copied to clipboard
Using __iter__ to retrigger the oscillators can cause unexpected problems
Iterable objects usually just return self from the __iter__ method, and quite a lot of code expects this to be the case. Resetting the oscillator there can lead to surprising results, for example:
import miniaudio
import numpy as np
from oscillators import SineOscillator
def stream_pcm(source):
required_frames = yield b"" # generator initialization
while True:
buf = np.fromiter(source, dtype=float, count=required_frames)*32767
required_frames = yield buf.astype(np.int16).tobytes()
if __name__ == '__main__':
sample_rate = 44100
with miniaudio.PlaybackDevice(output_format=miniaudio.SampleFormat.SIGNED16,
nchannels=1, sample_rate=sample_rate) as device:
source = SineOscillator(sample_rate=sample_rate)
stream = stream_pcm(source)
next(stream) # start the generator
device.start(stream)
input("Audio file playing in the background. Enter to stop playback: ")
It turns out that np.fromiter() calls iter() on whatever you pass it before calling next() count times, so the oscillator gets reset back to zero on every buffer callback. This causes glitchy playback.
An easy work around is just to hide the custom __iter__ method with a wrapper:
def wrapper(source):
yield from source
I think it would be better to have an explicit trigger or reset method, rather than re-purposing __iter__ for this.