pythonaudiopython-sounddevice

Sounddevice + big array + OOP = Segfault / Bus Error


I'm having a very strange problem, which I managed to reduce as much as possible like this:

import sounddevice
import time

class SamplerBox:
    def __init__(self):
        self.samples = {}

    def audio_callback(self, outdata, frame_count, time_info, status):
        print('ac')

    def init(self):
        self.connect_audio_output()
        self.load_samples()
        time.sleep(20)

    def connect_audio_output(self):
        try:
            sd = sounddevice.OutputStream(callback=self.audio_callback)
            sd.start()
            print('Opened audio device')
        except:
            print('Invalid audio device')
            exit(1)

    def load_samples(self):
        for midinote in range(128):
            for velocity in range(128):
                self.samples[midinote, velocity] = Sound()


class Sound:
    def __init__(self):
        pass


sb = SamplerBox()
sb.init()

As soon as I create that big self.samples dict, and only create a new audio stream with an empty callback, I get "Bus Error 10" with Python 3.11.

With Python 3.9 I get "Illegal instruction 4"

In my original script (reduced here) I got "Segmentation Fault 11"

I'm running Homebrew Python 3.11 on MacOS 10.15.7.

Worst than that, written in a procedural way, it runs perfectly :

import sounddevice
import time

samples = {}


class Sound:
    def __init__(self):
        pass

def audio_callback(self, outdata, frame_count, time_info, status):
    print('ac')


try:
    sd = sounddevice.OutputStream(callback=audio_callback)
    sd.start()
    print('Opened audio device')
except:
    print('Invalid audio device')
    exit(1)

for midinote in range(128):
    for velocity in range(128):
        samples[midinote, velocity] = Sound()

time.sleep(20)

Any idea?


Solution

  • You do not keep a reference to the output stream object created in connect_audio_output, so when the method finishes the sounddevice.OutputStream object associated to the local variable sd loses all its references and will be eventually collected by the GC.

    Keep the object alive, e.g. by assigning it to an instance attribute:

    def connect_audio_output(self):
        try:
            self.sd = sounddevice.OutputStream(callback=self.audio_callback)
            self.sd.start()
            print('Opened audio device')
        except:
            print('Invalid audio device')
            exit(1)
    

    In your procedural approach sd is a global variable, so the object will persist as long as the process exists.