spotify / pedalboard

🎛 🔊 A Python library for audio.
https://spotify.github.io/pedalboard
GNU General Public License v3.0
5.08k stars 256 forks source link

ASIO Drivers support? #211

Open aristizabal95 opened 1 year ago

aristizabal95 commented 1 year ago

Is there any way to stream audio to pedalboard through ASIO drivers? I can't see devices available through ASIO when looking at input and output device names. This is really necessary for me, since the audio latency I get using any other audio driver is unbearable.

psobot commented 1 year ago

Hi @aristizabal95!

Pedalboard is a Python wrapper for the JUCE C++ audio framework, and luckily, JUCE seems to have some ASIO support. I haven't tried this (and don't have a Windows machine handy to test on) but I'd be happy to test a PR that adds ASIO support by setting -DJUCE_ASIO=1 in setup.py.

aristizabal95 commented 1 year ago

Awesome! Thanks for pointing me in the right direction, I'll give it a try!

aristizabal95 commented 1 year ago

Thanks again for your suggestion! So I got some progress. I was able to build the project with ASIO drivers enabled following your instructions. Now, printing AudioStream.input_device_names shows all my available devices, including the ASIO ones. But, when I try to create an AudioStream with ASIO devices (in this case ASIO4ALL v2) I get ValueError: No such device: ASIO4ALL v2. Here's the code I'm trying to run:

from pedalboard.io import AudioStream
from pedalboard import Pedalboard, Compressor, Gain, Chorus, Phaser, Convolution, Reverb

print(AudioStream.input_device_names)
print("======================================")
print(AudioStream.output_device_names)

with AudioStream(input_device_name="ASIO4ALL v2", output_device_name="ASIO4ALL v2") as stream:
    stream.plugins = Pedalboard([
        Compressor(threshold_db=-50, ratio=25),
        Gain(gain_db=30),
        Chorus(),
        Phaser(),
        Reverb(room_size=0.25),
    ])
    input("Press enter to stop streaming...")

Taken from one of the repo examples

I'm trying to identify why this could be the case, but I must admit that my cpp knowledge is pretty much non-existent. If you have any ideas let me know!

aristizabal95 commented 1 year ago

Small update: I wanted to see if JUCE was also having issues using my ASIO Drivers. I built the DemoRunner with ASIO enabled and it is able to open my ASIO devices and stream audio through. Not sure if this is helpful in any way, but it at least seems like the issue is JUCE-independent.

psobot commented 1 year ago

Hm, that's interesting - I'm not a Windows user nor have I tried ASIO before, so I'm not that useful here unfortunately. Are you able to set just one of the input or output device names to ASIO? That No such device error comes from JUCE itself and it seems that JUCE compares devices based on a fuzzy match of their names, ignoring whitespace and case.

aristizabal95 commented 1 year ago

I'll look into that. Something I noticed by using JUCE DemoRunner was that when using ASIO devices you can't choose input/output separately. Instead, its only taking one device. I'll see if I can figure out something by reading the DemoRunner code, but my hypothesis is that trying to open an ASIO device as input/output is what's causing the problem.

Thanks for your help and responsiveness, @psobot! I'll keep you updated

ahorn42 commented 1 year ago

@aristizabal95 were you able to solve this? I tried the same like noted above and got the same error unfortunately.

aristizabal95 commented 1 year ago

@ahorn42 Sorry for the delay. I wasn't able to figure out the issue. I looked into my hypothesis of opening an ASIO device as input/output but in reality there's no difference between how JUCE and Pedalboard open devices, so I was left clueless again. I'd be happy to look into the code if there are any ideas on why this could be happening!

EParisot commented 1 month ago

Hi guys, looking at it since I got the same issue, looking at the code :

https://github.com/juce-framework/JUCE/blob/61a03097ec9e01693c87ac71935e97b9714cff1a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp#L294

It seems that it looks for the device's name (trimed and lowercased) in a filtered list of devices, filtered by type and isInput bool, looking at output first, input after...

Do you also got the same device name for input and output ?

Or maybe it is about device type ? (ASIO)

EParisot commented 1 month ago

UPDATE I made it work !

Here is how to :

in AudioStream.h line 73 :

    juce::String defaultDeviceName = inputDeviceName;
    juce::String error = deviceManager.initialise(2, 2, nullptr, true,
                                                  defaultDeviceName, nullptr);

Dont pass the AudioDeviceSetup arg (nullptr instead) but defaultDeviceName with an ASIO device name Just provide a non empty defaultDeviceName , I took the input requested name for convenience

I found out in the JUCE source code that it first tries to get the devices in and out from the AudioDeviceSetup, but fallback on the defaultDeviceName if no config, tried, recompiled and it works now !

https://github.com/juce-framework/JUCE/blob/61a03097ec9e01693c87ac71935e97b9714cff1a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp#L304

So Idk how to deal with it in a way that preserve both behaviours (non ASIO vs ASIO) but it is a good first step !

My understanding from the JUCE code is that if you provide a AudioDeviceSetup (with input and output names) it destroy the current device, then tries to get the current device type (!? that you just destroyed, ok it has a fallback in case it does not find its type, it takes the first type available in the types list and it is likely not ASIO), so it never evaluates asio type devices when searching for the input/output you provided. But when using the defaultDeviceName argument, and no config argument, it looks like it behave more confidently and get the device, get its type etc and manage to make it work properly.