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