LABSN / expyfun

Experimental paradigm functions.
BSD 3-Clause "New" or "Revised" License
13 stars 21 forks source link

Windows 11 ASIO devices not recognized #471

Open beisenreich opened 1 month ago

beisenreich commented 1 month ago

There appears to be an issue with using ASIO devices on windows 11 with the expyfun sound_controller _rtmixer.

1) It should be noted that the current release of sounddevice has ASIO disabled as default (see issue 496 ).

2) Rolling back sounddevice to version 0.4.7 produces the following behavior:

i. running python(3.11) and importing expyfun in the initial call to _rtmixer.py, ASIO devices are not found by sounddevice.query_devices() (line 55)

ii. when running python(3.11) and importing sounddevice and rtmixer directly, sounddevice.query_devices() does return the ASIO devices, and playback through the ASIO soundcard works

Has anyone else encountered this issue?

larsoner commented 1 month ago

Weird... no I have not encountered this before. It's odd because it seems like query_devices is the first thing that we do:

https://github.com/LABSN/expyfun/blob/21a3531afc6614ffb46afa942a0792c9ae5d062e/expyfun/_sound_controllers/_rtmixer.py#L55

beisenreich commented 1 month ago

It gets weirder. I did some more testing today and expyfun appears to be calling the right sounddevice library. I'm running version 0.4.7 which has the ASIO driver support included and calling sounddevice.version from line 56 of _rtmixer.py returns version 0.4.7. I thought maybe the issue is with the import of rtmixer loading a different sounddevice library, this also returned back as version 0.4.7. When I pull up the available hostapis expyfun can see from sounddevices I get a return of the ASIO but with devices=[].

Now the real weird part, if you import sounddevice before importing expyfun and initializing the ec. Everything works, version returns as 0.4.7, hostapis show ASIO driver support and devices return the Madiface USB. Thoughts?

larsoner commented 4 weeks ago

I'm not sure why the import order should matter. If you import python-rtmixer before sounddevice does it change the behavior?

beisenreich commented 3 weeks ago

Importing rtmixer before sounddevice does not impact the behavior. Instead it appears importing either rtmixer or sounddevice before importing or calling expyfun fixes the issue.

larsoner commented 3 weeks ago

:exploding_head: interesting... I wonder if we have code that sets a problematic env var or similar. Or maybe it's importing pyglet that is somehow problematic.

You could in theory try commenting out various parts of what import expyfun does (by digging into expyfun/__init__.py etc. and commenting stuff out) but for now I'm just relieved that there is some workaround for you!

hockinsk commented 3 weeks ago

devices = sounddevice.query_devices() with os.environ["SD_ENABLE_ASIO"] = "1" silently fails in many python environments (like a Qt GUI app for example). No errors, nothing as python will not even get passed importing sounddevice for anything to be written as an error. e.g. this works in a cli script to get an ASIO device at known index:

import os

# Enable ASIO on Windows if applicable
if os.name == "nt":
    os.environ["SD_ENABLE_ASIO"] = "1"
import sounddevice as sd

print("Starting device query...")

try:
    device = sd.query_devices(49) # Jack ASIO
    print(device)
except Exception as e:
    print(f"Error querying devices: {e}")

(venv) 
Starting device query...
{'name': 'JackRouter', 'index': 49, 'hostapi': 2, 'max_input_channels': 4, 'max_output_channels': 4, 'default_low_input_latency': 0.010666666666666666, 'default_low_output_latency': 0.010666666666666666, 'default_high_input_latency': 0.010666666666666666, 'default_high_output_latency': 0.010666666666666666, 'default_samplerate': 48000.0}

In a Qt6 window, this code below will never run until you remove os.environ["SD_ENABLE_ASIO"] = "1" or place import sounddevice as sd before PyQt import. Took a week to realise import order affects sounddevice and ASIO.

import os
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel
# Enable ASIO on Windows if applicable
if os.name == "nt":
   os.environ["SD_ENABLE_ASIO"] = "1"
import sounddevice as sd

print("Starting device query...")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # Set window title and dimensions
        self.setWindowTitle("Blank Qt6 GUI")
        self.setGeometry(100, 100, 800, 600)

        # Create a central widget
        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)

        # Set up layout and add a placeholder label
        layout = QVBoxLayout()
        label = QLabel("This is a blank Qt6 GUI.")
        layout.addWidget(label)
        central_widget.setLayout(layout)

        # Get ASIO device
        self.device = sd.query_devices(49) # Jack ASIO
        device_as_text = "\n".join([f"{key}: {value}" for key, value in self.device.items()])
        label.setText(device_as_text)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
larsoner commented 2 weeks ago

In a Qt6 window, this code below will never run until you remove os.environ["SD_ENABLE_ASIO"] = "1" or place import sounddevice as sd before PyQt import. Took a week to realise import order affects sounddevice and ASIO.

Yikes! No idea why this would be the case. I know PyQt6 (and in most recent PySide6 as well) does some magic with PyOS_Inputhook but not sure that would be related

hockinsk commented 2 weeks ago

Yeah, it's weird, I wasted so much time trying to figure out why nothing ASIO could ever be found with sounddevice depsite several rebuilds, custom libportaudio.dll builds etc etc and all it was was sounddevice needs to be imported first.