Acoustic Holography and Holophony
Franz Zotter, 2016
The Ambisonic panning function is derived from the equivalence that an infinite Fourier series equals a Dirac delta function:
\begin{equation} g_\infty(\varphi)=\sum_{m=-\infty}^\infty e^{\mathrm{i}m(\varphi)}=\sum_{m=0}^\infty(2-\delta_{m,0})\cos(m\varphi)=\delta(\varphi). \end{equation}After making the order finite, we obtain a sinc function
\begin{equation} g_\mathrm{N}(\varphi)=\sum_{m=0}^\mathrm{N}(2-\delta_{m,0})\cos(m\varphi)=\frac{\sin[(\mathrm{N}+\frac{1}{2})\varphi]}{\sin(\frac{1}{2}\varphi)}. \end{equation}
In [1]:
import numpy as np
import scipy as sp
import math
from bokeh.plotting import figure, output_file, show
from bokeh.io import push_notebook, output_notebook
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets
phi=np.linspace(-np.pi,np.pi,100)
output_notebook()
p1 = figure(title="5th order periodic sinc function",plot_width=400,plot_height=250)
N=5
normalization=2*N+1
sincp=np.sin((N+0.5)*phi)/np.sin(0.5*phi)
p1.line(phi*180/np.pi,normalization*sincp,line_width=3)
show(p1)
This kind of periodic sinc function could be cyclically shifted by any angle $\varphi_\mathrm{s}$ and would not change its shape. However, we are not quite satisfied with the height of its sidelobes. It is worth to involve weights $a_m$ for the suppression of these sidelobes:
\begin{equation} g_\mathrm{N}(\varphi)=\sum_{m=0}^\mathrm{N}a_m\,(2-\delta_{m,0})\cos(m\varphi). \end{equation}The so-called in-phase weighting (see Jérôme Daniel et al 1999) is defined as
\begin{equation} a_m=\frac{N!^2}{(N-m)!(N+m)!}=\frac{N^\underline{m}}{N^\overline{m}}, \end{equation}and the so-called max-$\boldsymbol{r}_\mathrm{E}$ weighting is
\begin{equation} a_m=\cos\Bigl(\frac{\pi}{2(\mathrm{N}+1)}\Bigr), \end{equation}and the neutral,rectangular weighting with $a_m=1$ is called basic.
In [2]:
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
N=7
p2 = figure(title="7th-order weights",plot_width=400,plot_height=250)
m=np.arange(0,N+1)
a=basic_weights(N)
p2.line(m,a,color="green",line_width=3,legend="basic")
a=inphase_weights(N)
p2.line(m,a,color="red",line_width=3,legend="in-phase")
a=maxre_weights(N)
p2.line(m,a,color="blue",line_width=3,legend="max-rE")
p2.legend.background_fill_alpha=0.4
show(p2)
In [3]:
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
p3 = figure(title="weighted cosine series",plot_width=400,plot_height=250)
a=basic_weights(N)
g=weighted_cosine_series(phi,a)
p3.line(phi*180/np.pi,g,line_width=3,color='green',legend="rectangular")
a=inphase_weights(N)
g=weighted_cosine_series(phi,a)
p3.line(phi*180/np.pi,g,line_width=3,color='red',legend="in-phase")
a=maxre_weights(N)
g=weighted_cosine_series(phi,a)
p3.line(phi*180/np.pi,g,line_width=3,color='blue',legend="max-rE")
show(p3)
In [4]:
def g_ambipan(N,phis,phi,weight_type):
g=np.zeros(phi.size)
ampl=0
if weight_type == 1:
a=inphase_weights(N)
elif weight_type == 2:
a=maxre_weights(N)
else:
a=basic_weights(N)
g=weighted_cosine_series(phi-phis,a)
return g
In [5]:
output_notebook()
g=g_ambipan(5,0,phi,0)
p = figure(title="2D Ambi Panning Function",plot_width=400, plot_height=270, x_range=(-180,180), y_range=(-.4,1.1))
ll=p.line(phi*180/np.pi, g , line_width=3)
In [6]:
def plot_ambipan(N,phis,weight_type):
phi=np.linspace(-np.pi,np.pi,100)
g=g_ambipan(N,phis*np.pi/180,phi,weight_type)
ll.data_source.data['y']=g
push_notebook()
In [7]:
show(p,notebook_handle=True)
Out[7]:
In [8]:
interact(plot_ambipan,N=(0,10,1), phis=(-180.0,180.0,1.0),weight_type={'in-phase':1,'max-rE':2,'basic':3});