This code example is to animate the 2D Ambisonic panning functions interactively

Acoustic Holography and Holophony

Franz Zotter, 2016

This animation is about what discretizing the Ambisonic panning function on the circle does to the rE and rV measures.

Some function definitions and headers first.


In [1]:
import numpy as np
import scipy as sp
import math
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook


def inphase_weights(N):
    a=np.ones(N+1)
    for n in range(1,N+1):
        a[n]=(N-n+1)/(1.0*(N+n))*a[n-1]
    return a

def maxre_weights(N):
    m=np.arange(0,N+1)
    a=np.cos(np.pi/(2*(N+1))*m)
    return a

def basic_weights(N):
    a=np.ones(N+1)
    return a

def weighted_cosine_series(phi,a):
    N=a.size-1
    g=np.zeros(phi.size)
    amplitude=0;
    for m in range(0,N+1):
        g+=np.cos(m*phi)*a[m]*(2-(m==0))
        amplitude+=a[m]*(2-(m==0))
    return g/amplitude

def rvector(gls,phils):
    glsc=np.copy(gls)
    glsc=np.dot(np.diag(1/np.sum(glsc,1)),glsc)
    r=np.array([np.dot(glsc,np.cos(phils)),np.dot(glsc,np.sin(phils))])
    return r

def get_ambipan_loudspeaker_gains(N,phis,phils,weight_type):
    g=np.zeros(phils.size)
    if weight_type == 'in-phase':
        a=inphase_weights(N)
    elif weight_type == 'max-rE':
        a=maxre_weights(N)
    else:
        a=basic_weights(N)
    g=weighted_cosine_series(phils-phis,a)
    return g

Let's take as an example $\mathrm{L}=7$ regularly spaced loudspeakers and the basic, rectangular weighting for panning on the horizon as an example for $5^\mathrm{th}$-order Ambisonic. As this is a $t=6$-design, it should already be able to perfectly control the $\boldsymbol{r}_\mathrm{V}$ vector.


In [13]:
L=7
Npt=200
phils=np.mod((2*np.pi*np.arange(0,L))/L+np.pi,2*np.pi)-np.pi
phis=np.linspace(-np.pi*0.99,np.pi,Npt)
gls=np.zeros((Npt,L))
for n in range(0,Npt):
    gls[n,:]=get_ambipan_loudspeaker_gains(5,phis[n],phils,'basic')

output_notebook()
p1 = figure(title="rE/rV directions of 5th order basic Ambi panning on 7 loudspeaeker",plot_width=600, plot_height=270, x_range=(-180,180), y_range=(-180.0,180.0))
p2 = figure(title="rE/rV widths of 5th order basic Ambi panning on 7 loudspeaeker",plot_width=600, plot_height=270, x_range=(-180,180),y_range=(-3,100))

rE=rvector(gls**2,phils)
dirE=np.arctan2(rE[1],rE[0])*180/np.pi;
lenE=np.sqrt(np.sum(rE**2,0))
sigmaE=2*np.arccos(lenE)*180/np.pi
p1.line(phis*180/np.pi, dirE, color="red",line_width=3,legend="rE basic")
p2.line(phis*180/np.pi, sigmaE, color="red",line_width=3,legend="rE basic")
rV=rvector(gls,phils)
dirV=np.arctan2(rV[1],rV[0])*180/np.pi;
lenV=np.sqrt(np.sum(rV**2,0))
sigmaV=2*np.arccos(lenV)*180/np.pi
p1.line(phis*180/np.pi, dirV, color="blue",line_width=3,legend="rV basic",line_dash=(6,2))
p2.line(phis*180/np.pi, sigmaV, color="blue",line_width=3,legend="rV basic",line_dash=(6,2))

p1.legend.background_fill_alpha = 0.5
p2.legend.background_fill_alpha = 0.5
show(p1)
show(p2)


Loading BokehJS ...

Taking $\mathrm{L}=12$, a $t=11$-design is already able to perfectly control the $\boldsymbol{r}_\mathrm{E}$ vector as well for $5^\mathrm{th}$ order basic-weighted panning.


In [14]:
L=12
Npt=200
phils=np.mod((2*np.pi*np.arange(0,L))/L+np.pi,2*np.pi)-np.pi
phis=np.linspace(-np.pi*0.99,np.pi,Npt)
gls=np.zeros((Npt,L))
for n in range(0,Npt):
    gls[n,:]=get_ambipan_loudspeaker_gains(5,phis[n],phils,'basic')

output_notebook()
p3 = figure(title="rE/rV directions of 5th order basic Ambi panning on 12 loudspeaeker",plot_width=600, plot_height=270, x_range=(-180,180), y_range=(-180.0,180.0))
p4 = figure(title="rE/rV widths of 5th order basic Ambi panning on 12 loudspeaeker",plot_width=600, plot_height=270, x_range=(-180,180),y_range=(-3,100))

rE=rvector(gls**2,phils)
dirE=np.arctan2(rE[1],rE[0])*180/np.pi;
lenE=np.sqrt(np.sum(rE**2,0))
sigmaE=2*np.arccos(lenE)*180/np.pi
p3.line(phis*180/np.pi, dirE, color="red",line_width=3,legend="rE basic")
p4.line(phis*180/np.pi, sigmaE, color="red",line_width=3,legend="rE basic")
rV=rvector(gls,phils)
dirV=np.arctan2(rV[1],rV[0])*180/np.pi;
lenV=np.sqrt(np.sum(rV**2,0))
sigmaV=2*np.arccos(lenV)*180/np.pi
p3.line(phis*180/np.pi, dirV, color="blue",line_width=3,legend="rV basic",line_dash=(6,2))
p4.line(phis*180/np.pi, sigmaV, color="blue",line_width=3,legend="rV basic",line_dash=(6,2))

p3.legend.background_fill_alpha = 0.5
p4.legend.background_fill_alpha = 0.5
show(p3)
show(p4)


Loading BokehJS ...

It also worlks for other weightings automatically. Let's for instance inspect the example for $\mathrm{max}-\boldsymbol{r}_\mathrm{E}$.


In [15]:
L=12
Npt=200
phils=np.mod((2*np.pi*np.arange(0,L))/L+np.pi,2*np.pi)-np.pi
phis=np.linspace(-np.pi*0.99,np.pi,Npt)
gls=np.zeros((Npt,L))
for n in range(0,Npt):
    gls[n,:]=get_ambipan_loudspeaker_gains(5,phis[n],phils,'max-rE')

output_notebook()
p5 = figure(title="rE/rV directions of 5th order max-rE Ambi panning on 12 loudspeaeker",plot_width=600, plot_height=270, x_range=(-180,180), y_range=(-180.0,180.0))
p6 = figure(title="rE/rV widths of 5th order max-rE Ambi panning on 12 loudspeaeker",plot_width=600, plot_height=270, x_range=(-180,180),y_range=(-3,100))

rE=rvector(gls**2,phils)
dirE=np.arctan2(rE[1],rE[0])*180/np.pi;
lenE=np.sqrt(np.sum(rE**2,0))
sigmaE=2*np.arccos(lenE)*180/np.pi
p5.line(phis*180/np.pi, dirE, color="red",line_width=3,legend="rE basic")
p6.line(phis*180/np.pi, sigmaE, color="red",line_width=3,legend="rE basic")
rV=rvector(gls,phils)
dirV=np.arctan2(rV[1],rV[0])*180/np.pi;
lenV=np.sqrt(np.sum(rV**2,0))
sigmaV=2*np.arccos(lenV)*180/np.pi
p5.line(phis*180/np.pi, dirV, color="blue",line_width=3,legend="rV basic",line_dash=(6,2))
p6.line(phis*180/np.pi, sigmaV, color="blue",line_width=3,legend="rV basic",line_dash=(6,2))

p5.legend.background_fill_alpha = 0.5
p6.legend.background_fill_alpha = 0.5
show(p5)
show(p6)


Loading BokehJS ...

Obviously this is more optimal in terms of the $\boldsymbol{r}_\mathrm{E}$ vector (it is now indicating $30^\circ$ spread only instead of $50^\circ$) and perfect for both measures.


In [ ]: