0

I'm trying to play a stereo signal using these two phase shifted tones and I can't make it

import numpy as np
import sounddevice as sd

fs = 44100
duration = 1
frequency = 440

phase = 90 * 2 * np.pi / 360
sine_A = (np.sin(2 * np.pi * np.arange(fs * duration) * frequency / fs + phase)).astype(
    np.float32)
sine_B = (np.sin(2 * np.pi * np.arange(fs * duration) * frequency / fs)).astype(np.float32)

sumSine= np.array([sine_A, sine_B])
sd.play(sumSine)

And it returns me the following error:

sounddevice.PortAudioError: Error opening OutputStream: Invalid number of channels

I can't track which is the problem

2 Answers2

0

The problem was in the way I did the numpy array.

It must be like

stereoSignal = numpy.array([[a,b], [c,d],...])

The correct aproximation is below.

fs = 44100
duration = 1
frequency = 440

phase = 90 * 2 * np.pi / 360
sine_A = (np.sin(2 * np.pi * np.arange(fs * duration) * frequency / fs + phase)).astype(
    np.float32)
sine_B = (np.sin(2 * np.pi * np.arange(fs * duration) * frequency / fs)).astype(np.float32)

sumSine = np.array([list(i) for i in zip(sine_A, sine_B)])
sd.play(sumSine, mapping=[1, 2])

I attach the complete code in this link: https://github.com/abonellitoro/not-in-phase-stereo-tone-generator

  • I like to use something like `np.column_stack((sine_A, sine_B))`, because it shows quite clearly that A and B are supposed to be columns of the resulting array. BTW, the `mapping` is redundant, since playing a two-channel array on the first two channels is the default. What you should specify instead, is `samplerate`, because now you are using the default sampling rate, which might not be the one you are expecting. – Matthias Mar 26 '17 at 21:07
  • Also, `astype()` is not strictly necessary here, since `sd.play()` can handle `'float64'` arrays. – Matthias Mar 26 '17 at 21:09
0

As you've found out, you were not concatenating sine_A and sine_B correctly. The way you did it, an array with two rows and many columns was created. Since sd.play() expects the columns to be channels, you obviously gave it too many, that's why it complained.

You should concatenate the two array in a way that they form the two columns of a new array. I think the easiest way to do this is:

np.column_stack((sine_A, sine_B))

It will still not work, though. When you call sd.play(), it starts playing in the background and then immediately returns. Since you are exiting your script right after that, you won't really hear anything of your nice sine tone. To wait for the playback to be finished, you could e.g. use sd.wait().

In the end, your script might look something like this:

import numpy as np
import sounddevice as sd

fs = 44100
duration = 1
frequency = 440

phase = np.pi / 2
t = np.arange(int(fs * duration)) / fs
sine_A = np.sin(2 * np.pi * frequency * t + phase)
sine_B = np.sin(2 * np.pi * frequency * t)

stereo_sine = np.column_stack((sine_A, sine_B))
sd.play(stereo_sine, fs)
sd.wait()

BTW, you can also have a look at my little tutorial, which shows similar things.

Matthias
  • 4,524
  • 2
  • 31
  • 50
  • I am new to time-series signals -- just curious, is there a reason why something like multi-channel audio is represented as columns and not rows? – user391339 Dec 21 '20 at 23:14
  • 1
    @user391339 Both orderings are generally possible, and there are libraries out there which use rows instead of columns. It's mostly a matter of convention, and arguments can be made for either ordering. When using interleaved samples (which is common) and "Row-major (C-style)" ordering in NumPy arrays (which is the default), you automatically get the channels as colums. Also, when you plot a multi-channel NumPy array with Matplotlib, it works out-of-the-box (without additional transposing) when using columns. – Matthias Dec 22 '20 at 08:50