spatialaudio / python-sounddevice

:sound: Play and Record Sound with Python :snake:
https://python-sounddevice.readthedocs.io/
MIT License
982 stars 145 forks source link

Please add new example to "Example programs" #346

Open tvvladimir2 opened 3 years ago

tvvladimir2 commented 3 years ago

Please add a new example to the "Example programs"

Example name: Open and run stream with two channels on different devices

Description: Open a stream with two channels. Play each channel on different devices. Play audiofile1 on channel1 device 1 and play audiofile2 on channel2 on the device 2. Use callback function to start, stop and pause each channel separately.

Problem: I know how to open a stream, but I don't understand how to assign a NumPy array or single audio file to the stream, and especially to a specific channel of the stream on a particular device.

HaHeho commented 3 years ago

It could be reasonable to add an example code for that. Although, it's configuration would be always very system dependent.

With "different devices" you mean different audio interfaces simultaneously? If so, we proposed a solution for something similar in https://github.com/spatialaudio/python-sounddevice/issues/338 (it is simultaneous recording, but shows different proposals to implement the required multiprocessing/threading).

For the playback you probably want to adapt the solution in play_long_file.py (although, you probably looked at that already?).

If you are a facing a specific challenge for you problem, it would be more clear if you post a short code example is executable and shows where you are stuck.

tvvladimir2 commented 2 years ago

Hi HaHeho,

Here's the example code I have so far: Description: I have x2 GPIO buttons, x2 USB audio devices. Start stream with two channels for USB device1 and USB device2. If I press button1: if the music playing pause device1, channel1, if the music is not playing, start on device1, channel1. Same for the second button, but for device2, channel2.

import sounddevice as sd
import soundfile as sf
from time import sleep
import RPi.GPIO as GPIO
import threading
import argparse
import pathlib
import os
import numpy as np # do i use numpy or raw data?

GPIO.setmode(GPIO.BOARD)
button1=16
button2=12

s = sd.query_devices()
audio0 = f"""{s[0]["name"]})"""
audio1 = f"""{s[1]["name"]})"""
audio2 = f"""{s[2]["name"]})"""
audio3 = f"""{s[3]["name"]})"""
print(audio0) #bcm2835 HDMI 1: - (hw:0,0))
print(audio1) #bcm2835 Headphones: - (hw:1,0))
print(audio2) #USB Audio Device: - (hw:2,0))
print(audio3) #USB Audio Device: - (hw:3,0))

GPIO.setup(button1,GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.setup(button2,GPIO.IN,pull_up_down=GPIO.PUD_UP)

filename1 = '1.wav'
filename2 = '2.wav'

data1, fs1 = sf.read(filename1, dtype='float32')
data2, fs2 = sf.read(filename2, dtype='float32')

BS1=False # Set condition variables, not very operable. This is not good. Better check if audio is playing or not.
BS2=False

stream1 = sd.RawOutputStream(device=None, channels=2, dtype='float32')
stream1.start()

while(1):
    if GPIO.input(button1)==0:
        print ("Button 1 was pressed")
        if BS1==False:
            stream1.play(data1, channel=1, device=0) # Not working # How do I start stream on device 0, channel 1, data1 ?
            # I don't understand how to pass data1 to stream1 to different devices simultaniously
            print("Device=0 playback started")
            BS1=True
            sleep(.5)
        else:
            stream1.stop(data1, channel=1, device=0) # Not working # How do I stop stream on device 0, channel 1, data1 ?
            print("Device 0 playback stopped")
            BS1=False
            sleep(.5)

    if GPIO.input(button2)==0:
        print ("Button 2 was pressed")
        if BS2==False:
            stream1.play(data2, channel=2, device=1) # Not working # How do I start stream on device 1, channel 2, data2 ?
            print("Device 1 playback started")
            BS2=True
            sleep(.5)
        else:
            stream1.play(data2, channel=2, device=1) # Not working # How do I stop stream on device 1, channel 2, data2 ?
            print("Device 1 playback stopped")
            BS2=False
            sleep(.5)
HaHeho commented 2 years ago

Thanks for fixing the code formatting. :) You can have ```python so we also get the appropriate syntax highlighting.

The example you've provide is quite messy of course. And it's impossible to test on a different machine, since we don't have the hardware nor the audio files available. But here a first try to get started (all untested). See how or if that works, and we can get more fancy from there.

import RPi.GPIO as GPIO
import sounddevice as sd
import soundfile as sf

GPIO.setmode(GPIO.BOARD)
button1 = 16
button2 = 12
GPIO.setup(button1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(button2, GPIO.IN, pull_up_down=GPIO.PUD_UP)

s = sd.query_devices()
audio0 = f'{s[0]["name"]})'
audio1 = f'{s[1]["name"]})'
audio2 = f'{s[2]["name"]})'
audio3 = f'{s[3]["name"]})'
print(audio0)  # bcm2835 HDMI 1: - (hw:0,0))
print(audio1)  # bcm2835 Headphones: - (hw:1,0))
print(audio2)  # USB Audio Device: - (hw:2,0))
print(audio3)  # USB Audio Device: - (hw:3,0))
device1 = 0
device2 = 1

filename1 = "1.wav"
filename2 = "2.wav"
data1, fs1 = sf.read(filename1, dtype="float32")
data2, fs2 = sf.read(filename2, dtype="float32")

# Set condition variables, not very operable. This is not good. Better check if audio is playing or not.
BS1 = False
BS2 = False

try:
    while True:
        if GPIO.input(button1) == 0:
            print("Button 1 was pressed")
            if not BS1:
                print(f"Starting playback device={device1} ...")
                sd.play(data=data1, samplerate=fs1, device=device1, blocking=False)
                BS1 = True
                sd.sleep(0.5)
            else:
                print(f"Stopping playback device={device1} ...")
                sd.stop()
                BS1 = False
                sd.sleep(0.5)

        if GPIO.input(button2) == 0:
            print("Button 2 was pressed")
            if not BS2:
                print(f"Starting playback device={device2} ...")
                sd.play(data=data2, samplerate=fs2, device=device2, blocking=False)
                BS2 = True
                sd.sleep(0.5)
            else:
                print(f"Stopping playback device={device2} ...")
                sd.stop()
                BS2 = False
                sd.sleep(0.5)

except KeyboardInterrupt:
    print("\nInterrupted by user.")

There is still plenty of quirks in there, why this is for sure not a good implementation, e.g.

tvvladimir2 commented 2 years ago

Hi HaHeho,

You are correct sd.play() and sd.stop() is not an option. I need to make simultaneous playback possible. So Stream() needs to be used. I introduce a stream with two channels, 1 channel for device and 1 channel for device 1: stream1 = sd.RawOutputStream(device=None, channels=2, dtype='float32') Then I, start the stream stream1.start() Now I have to to pass an audio file, start audio playback on stream for different devices and be able to pause it. I don't understand how to do it. Perhaps use stream.write() ? Tried it, didn't work for me.

HaHeho commented 2 years ago

You are correct sd.play() and sd.stop() is not an option.

Yes, but do they work (in a sense of that playback to the different interfaces works as expected)?

I need to make simultaneous playback possible. So Stream() needs to be used. I introduce a stream with two channels, 1 channel for device and 1 channel for device 1: stream1 = sd.RawOutputStream(device=None, channels=2, dtype='float32') Then I, start the stream stream1.start() Now I have to to pass an audio file, start audio playback on stream for different devices and be able to pause it. I don't understand how to do it. Perhaps use stream.write() ? Tried it, didn't work for me.

Starting the stream as correct, although you'll probably need two independent streams for the playback to either device. Then you are missing the part to actually fill the data1 and data2 (in chunks) into the stream. You can see in play_file.py or play_long_file.py how this can be done (in the callback).