Skip to content

Using __iter__ to retrigger the oscillators can cause unexpected problems #2

@ali1234

Description

@ali1234

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions