This code example is to demonstrate 2D Ambisonic panning functions interactively

Acoustic Holography and Holophony

Franz Zotter, 2016

Ambisonic Panning function in 2D

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)


Loading BokehJS ...

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)


Loading BokehJS ...

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

<Bokeh Notebook handle for In[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});