Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: '_AudioCallbackUserdata' object is not callable #128

Closed
splatpope opened this issue May 27, 2023 · 3 comments
Closed

TypeError: '_AudioCallbackUserdata' object is not callable #128

splatpope opened this issue May 27, 2023 · 3 comments
Assignees
Labels

Comments

@splatpope
Copy link

splatpope commented May 27, 2023

``I am trying to repeatedly play a very small sound sample (1/60th of a second) using tcod's sdl.audio module.

Since I am having trouble synchronizing the audio buffer, I decided to try the pull approach with a callback.

Here is a minimal example :

import tcod
import tcod.sdl.audio
import numpy as np
from scipy import signal

device = tcod.sdl.audio.open(callback=True)

sample_rate = device.frequency
n_samples = device.buffer_samples
duration = n_samples // sample_rate

print(device.format)

t = np.linspace(0, duration, n_samples, endpoint=False)
wave = signal.square(t * 2 * np.pi * 440).astype(device.format)
wave = device.convert(wave)

def pull_wave(device: tcod.sdl.audio.AudioDevice, stream: np.ndarray):
    stream.data = wave.data

device.callback = pull_wave

And here is the error I am getting (same as with the more complicated usage inside my application) :

Exception ignored from cffi callback <function _sdl_audio_callback at 0x00000181C7418D30>:
Traceback (most recent call last):
  File "C:\Users\Splatpope\AppData\Roaming\Python\Python39\site-packages\tcod\sdl\audio.py", line 486, in _sdl_audio_callback
    data: _AudioCallbackUserdata = ffi.from_handle(userdata)()
TypeError: '_AudioCallbackUserdata' object is not callable
forrtl: error (200): program aborting due to control-C event
Image              PC                Routine            Line        Source
libifcoremd.dll    00007FFF7C4BDF54  Unknown               Unknown  Unknown
KERNELBASE.dll     00007FFFD5A929F3  Unknown               Unknown  Unknown
KERNEL32.DLL       00007FFFD66E7614  Unknown               Unknown  Unknown
ntdll.dll          00007FFFD81E26A1  Unknown               Unknown  Unknown

Platform : Windows 10
Python version : 3.9.13
tcod version from pip : 15.0.1

Notes :

  • I have not managed to find any examples of audio callback usage with python-tcod anywhere, so my implementation might be flawed
@HexDecimal HexDecimal added the bug label May 28, 2023
@HexDecimal
Copy link
Collaborator

I've fixed the TypeError, this is my version of your example which will run on the next release of tcod:

import math
import time
from typing import Any

import numpy as np
from numpy.typing import NDArray
from scipy import signal  # type: ignore[import]

import tcod.sdl.audio

VOLUME = 0.04


class PullWave:
    def __init__(self) -> None:
        self.time = 0.0

    def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None:
        sample_rate = device.frequency
        n_samples = device.buffer_samples
        duration = n_samples / sample_rate
        print(f"{duration=} {self.time=}")

        t = np.linspace(self.time, self.time + duration, n_samples, endpoint=False)
        self.time += duration
        wave = signal.square(t * (math.tau * 440)).astype(np.float32)
        wave *= VOLUME

        stream[:] = device.convert(wave)


with tcod.sdl.audio.open(callback=PullWave()) as device:
    print(device)
    time.sleep(1)

I smoothed the waveform and reduced the volume. Sorry for the lack of documentation around this, you seemed to have done pretty well in spite of that.

I'm also interested in what you were having trouble with before you switched to callbacks.

@HexDecimal HexDecimal self-assigned this May 28, 2023
@splatpope
Copy link
Author

splatpope commented May 28, 2023

  • Generating the samples during the callback doesn't seem like best practices; I've seen in SDL-related discussion that it should only be used to copy data, but I suppose that doesn't matter if the generation and copy is actually done before the next callback invocation
  • BUT your approach has the obvious advantage of keeping track of time and making sure the waveform is continuous over successive callbacks. I don't think I'll need that but it's quite a neat trick. It would be nice to provide more examples like this in the official docs.
  • The reason I switched to the pull method is because the push method requires synchronizing the buffer's filling with whatever you're playing (playing a buffer that is not full produces noisy artifacts especially when repeatedly playing a very small sample), which gave me multiple headaches. Other SDL-related discussions conclude that the pull method is much more advantageous, especially when mixing UI and DSP logic, which shouldn't be done at all when using the push method (iirc, because timing issues will arise and be a hell to manage)

Finally, are you meaning to say that the error is already fixed, and will not be present in the next version of tcod ?
EDIT : I've looked at the current state of tcod/sdl/audio.py and it seems to be the case

In this case, can you tell me when it will release and be available on pypi? Alternatively, how could I be able to install the current codebase ? (I suppose the easiest way would be to clone, build, install local package with pip)

@HexDecimal
Copy link
Collaborator

You're correct, you do want to be careful what you do in the audio callback. I did it this way to keep artifacts out of the waveform. Better math could be used to make a looping pattern instead.

I still need to look into push vs pull. I leaned towards push because Python doesn't like it as much when it gets called into from C, but that seems to work a little better since I starting passing exceptions to the unraisable hook. The mixer I experimented with can probably be modified to work with the pull method. Keep in mind that with either method you'll have to worry about the Python GIL and that most large Numpy operations will release the GIL for their duration. Any pure-Python implementation of audio handling is going to fall behind in performance, so try to do as much with Numpy as possible.

I already made new a release on PyPI, then I made another release with more bugfixes. If you got it from pip before, then you can upgrade your installation with: pip install -U tcod.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants