music.localize

music.localize(sonic_vector=None, theta=None, distance=0, x=0.1, y=0.01, zeta=0.215, air_temp=20, sample_rate=None)[source]

Make a mono sound stereo and localize it by a very naive method.

See below for implementation notes.

Parameters:
sonic_vectorarray_like

A one-dimensional array with the PCM samples of the sound.

thetascalar or None

The azimuthal angle of the position in degrees, measured from the ear axis as in localize_linear(): 0 is the right ear’s side, 90 straight ahead and 180 the left. If it is given, x and y are ignored and distance must also be given for the localization to have effect. None, the default, places the source at x and y.

distancescalar

The distance of the source from the listener in meters.

xscalar

The lateral component of the position in meters.

yscalar

The frontal component of the position in meters.

zetascalar

The distance between the ears in meters.

air_tempscalar

The temperature in Celsius used for calculating the speed of sound.

sample_rateinteger or None

The sample rate. None, the default, is the rate music.get_sample_rate() returns.

Returns:
sndarray

A (2, nsamples) shaped array with the PCM samples of the stereo sound.

Raises:
ValueError

If sonic_vector is not one-dimensional. A stereo sound reached numpy as a message about array dimensions; the routine places one source, so mix the channels down first.

If zeta is negative: it is the distance between the ears, and below zero the interaural delay reached numpy as an array of negative length.

See also

reverb

A reverberator.

localize2

a less naive implementation of localization by ITD and IID.

Notes

The ears are zeta meters apart on the x axis, either side of the origin, and the source is at \((x, y)\), or distance meters towards theta. Its distance from each ear is a straight line, the article’s eq:distOuvidos [1], and the farther ear hears it later by the difference over the speed of sound (eq:dti) and quieter by the ratio (eq:dii):

\[ \begin{align}\begin{aligned}d_r = \sqrt{(x - \zeta/2)^2 + y^2}, \qquad d_l = \sqrt{(x + \zeta/2)^2 + y^2},\\\Lambda_{ITD} = \left\lfloor \frac{|d_l - d_r|}{c} f_s \right\rfloor, \qquad \frac{\min(d_l, d_r)}{\max(d_l, d_r)},\end{aligned}\end{align} \]

the delay in whole samples and the gain on the farther ear, with \(c = 331.3 + 0.606\,T\) m/s, the speed of sound at air_temp \(T\) (eq:locImpl). localize_linear() delays by fractions of a sample.

These are the most naive interaural cues [1]. Near the head – a distance under about 0.3 m – they place a source at theta convincingly, and they are fast, simple and true to how sound travels, though they know nothing of the body but where the ears are. localize2() is less naive, and a head-related transfer function more convincing than either: localize_hrtf() applies a measured one, which hrir() reads.

Every direction on a cone around the ear axis – the ‘cone of confusion’ – gives the same delay and the same ratio, so these cues cannot tell front from back, nor say how high a source is; those come from the head and the outer ears, which an HRTF measures [1]. Nor is the diffraction modelled that brings a lateral source’s low frequencies round the head to the far ear, about 0.7 ms later [1].

The value of zeta is about 0.215 m for an adult [1].

Cite the following article whenever you use this function.

References

[1]

Fabbri, Renato, et al. “Musical elements in the discrete-time representation of sound.” arXiv preprint arXiv:abs/1412.6853 (2017)

Examples

>>> write_wav_stereo(localize())
>>> write_wav_stereo(horizontal_stack(*[
...     localize(note_with_vibrato(duration=1), x=i, y=j)
...     for i, j in zip([.1, .7, np.pi - .1, np.pi - .7],
...                     [.1, .1, .1, .1])]))