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)
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)
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)
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 [ ]: