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.


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
import ipywidgets as widgets
import time, threading


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/=sum(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


L_widget = widgets.IntSlider(min=4, max=12, step=1,value=8,description="L")
phis_widget= widgets.FloatSlider(min=-180.0,max=180.0,step=1.0,value=0.0,description="phi")
weights_widget= widgets.SelectionSlider(options=['basic','max-rE','in-phase'],value='basic',description="weight")
animate_widget=widgets.Checkbox(value=False,description="anim")

L=L_widget.value;
phils=np.mod((2*np.pi*np.arange(0,L))/L+np.pi,2*np.pi)-np.pi
phis=phis_widget.value*np.pi/180.0
phi=np.linspace(-np.pi,np.pi,100)
weight=weights_widget.value
g=get_ambipan_loudspeaker_gains(5,phis,phi,weight)
gls=get_ambipan_loudspeaker_gains(5,phis,phils,weight)

output_notebook()
p = figure(title="2D 5th-order Ambi Panning Function",plot_width=600, plot_height=270, x_range=(-180,180), y_range=(-.4,1.1))
cc=p.line(phi*180/np.pi, g, line_width=1, color="blue")
ll=p.circle(phils*180/np.pi, gls , line_width=3, color="red")
pp=p.line(np.array([phis, phis])*180/np.pi, np.array([0, 1]), color="black")
rE=rvector(gls**2,phils)
dirE=np.arctan2(rE[1],rE[0])*180/np.pi;
lenE=np.sqrt(np.sum(rE**2))
prE=p.line(np.array([1, 1])*dirE, np.array([0, 1])*lenE, color="red",line_width=3,legend="rE")
rV=rvector(gls,phils)
dirV=np.arctan2(rV[1],rV[0])*180/np.pi;
lenV=np.sqrt(np.sum(rV**2))
prV=p.line(np.array([1, 1])*dirV, np.array([0, 1])*lenV, color="green",line_width=3,legend="rV")
show(p,notebook_handle=True)

def update_plot(phils,phis,weight):
    pp.data_source.data['x']=np.mod(np.array([phis, phis])*180/np.pi+180,360)-180
    phi=np.linspace(-np.pi,np.pi,100)
    g=get_ambipan_loudspeaker_gains(5,phis,phi,weight)
    cc.data_source.data['y']=g
    gls=get_ambipan_loudspeaker_gains(5,phis,phils,weight)
    ll.data_source.data['y']=gls
    ll.data_source.data['x']=phils*180/np.pi
    rE=rvector(gls**2,phils)
    dirE=np.arctan2(rE[1],rE[0])*180/np.pi;
    lenE=np.sqrt(np.sum(rE**2))
    prE.data_source.data['x']=np.array([1, 1])*dirE
    prE.data_source.data['y']=np.array([0, 1])*lenE
    rV=rvector(gls,phils)
    dirV=np.arctan2(rV[1],rV[0])*180/np.pi;
    lenV=np.sqrt(np.sum(rV**2))
    prV.data_source.data['x']=np.array([1, 1])*dirV
    prV.data_source.data['y']=np.array([0, 1])*lenV
    push_notebook()

def on_value_change(change):
    L=L_widget.value
    phils=np.mod((2*np.pi*np.arange(0,L))/L+np.pi,2*np.pi)-np.pi
    phis=phis_widget.value*np.pi/180.0
    weight=weights_widget.value
    update_plot(phils,phis,weight)

#widgets.jslink((play,'value'),(phis_widget,'value'))
#interactive(update_plot, L=L_widget, phis=phis_widget, weight=weights_widget)

phis_widget.observe(on_value_change,names='value')
L_widget.observe(on_value_change,names='value')
weights_widget.observe(on_value_change,names='value')

def animate_plot(change):
    if animate_widget.value:
        phis=phis_widget.value
        phis=np.mod(phis+3+180,360)-180
        phis_widget.value=phis
        threading.Timer(0.008, animate_plot,[1]).start()

animate_widget.observe(animate_plot,names='value')
widgets.HBox([phis_widget,L_widget,weights_widget,animate_widget])


Loading BokehJS ...

In [ ]: