Animating kinematic vocal folds#
The lt.KinematicVocalFolds
class is furnished with the method draw3d()
to make 3D visualization of the vocal-fold model easier. This example demonstrates how to accomplish the 3D animation with matplotlib.animation.FuncAnimation in Jupyter Notebook.
First we load the necessary libraries
[1]:
%matplotlib agg
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import letalker as lt
Symmetric Periodic Vibration#
The key to the animation is controlling the number of animation frames per glottal cycle. For a basic demonstration of a periodic vibration, we only need one cycle, and let the animation engine to repeat it at much slower speed than the real time, (similar to playing back highspeed videoendoscopy video at much slower rate).
For the first example, let’s use the default symmetric vocal folds at \(f_o=\) 100 Hz.
[2]:
fo = 100
vf = lt.KinematicVocalFolds(fo)
Now, we need to set up the simulation rate and duration.
We only need one glottic cycle, which lasts in 10 ms. With the default sampling rate of 44.1 kHz, we would end up with 441 frames per cycle, which is too much detail necessary.
Instead, let’s aim for 30 frames per cycle by changing the sampling rate, then run the model for 1 cycle or 10 ms. Also, set the animation to play back at the rate of 1 cycle per second.
[3]:
fpc = 30 # target frames per cycle
fs = fpc * fo # new sampling rate to meet fpc target for the specified fo
# apply the new sampling rate to pyLeTalker
lt.core.set_sampling_rate(fs)
print(f"new sampling rate = {lt.fs} Hz")
new sampling rate = 3000 Hz
Now, the 3D model at each sample instance is drawn on Matplotlib’s 3D axes, and the animation is iteratively recorded by FuncAnimation
as follows
[4]:
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.view_init(22.5, -70) # good perspective view
# ax.view_init(90, -90) # top view
ax.set_proj_type("ortho")
ax.set_aspect("equal")
def animate(i):
vf.draw3d(i, axes=ax)
ax.set_title(f'$t={i/fs:0.3f}$')
ani = FuncAnimation(fig, animate, frames=fpc, interval=1 / fpc)
Finally, we can show the animation in Jupyter Notebook with the following command:
[5]:
HTML(ani.to_jshtml(fpc))
[5]:
Entrained left-right biphonation#
How about a case of 2:3 biphonation? Keeping one vocal fold to vibrate at 100 Hz, the other vocal fold would then vibrate at 150 Hz. These yield the true/overall fundamental frequency of 50 Hz, 100-Hz vocal fold vibrates twice while 150-Hz one vibrates thrice. Let the 150-Hz vocal fold to be animated with the same 30 frames/cycle recording rate and playback at 1 second/cycle speed. This needs the animation to be 3-cycles long or 90 frames, and we need the sampling rate of 4.5 kHz (150 Hz \(\times\) 3).
[6]:
fo1 = 100
fo2 = 150
vf = lt.KinematicVocalFolds([fo1, fo2])
fs = fpc * fo2 # new sampling rate to meet fpc target for the specified fo
# apply the new sampling rate to pyLeTalker
lt.core.set_sampling_rate(fs)
print(f"new sampling rate = {lt.fs} Hz")
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.view_init(22.5, -70) # good perspective view
# ax.view_init(90, -90) # top view
ax.set_proj_type("ortho")
ax.set_aspect("equal")
ani = FuncAnimation(fig, animate, frames=3*fpc, interval=1 / fpc)
HTML(ani.to_jshtml(fpc))
new sampling rate = 4500 Hz
[6]: