Skip to content

Conversation

narahahn
Copy link
Member

@narahahn narahahn commented Sep 2, 2025

Add Jupyter notebook for generating an animation of a synthesized sound field.

wfs-25d-td

@narahahn narahahn requested a review from fs446 September 3, 2025 08:35
@hagenw
Copy link
Member

hagenw commented Sep 4, 2025

Cool, I remember those plots ;)

Copy link
Member

@mgeier mgeier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add your example to doc/examples.rst (and give it a title), so that it appears in our brand-new example gallery: https://sfs-python--193.org.readthedocs.build/en/193/examples.html

Here's how it looks in the docs: https://sfs-python--193.org.readthedocs.build/en/193/examples/wfs-animation.html

" return [im]\n",
"\n",
"\n",
"time_stamps = np.linspace(0.5/343, 5/343, 100) # Time sampling is different from fs defined above\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number 343 is suspicious here. If you want to use the speed of sound, you should use sfs.default.c, if not, it would probably be better to use different values.

"\n",
"# Impulsive excitation\n",
"fs = 8000 # Adjust this to change the shape (width) of the impulse\n",
"signal = unit_impulse(512), fs # Band-limited pulse (e.g. sinc) can be used instead\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is unit_impulse really necessary here? What about using this?

signal = [1], fs

Comment on lines +71 to +72
"p = sfs.td.synthesize(d, selection, array, secondary_source, grid=grid,\n",
" observation_time=0)\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this does anything, right?

Suggested change
"p = sfs.td.synthesize(d, selection, array, secondary_source, grid=grid,\n",
" observation_time=0)\n",

It just creates p which is then never used?

"im = plot(d, selection, secondary_source, t=ts, ax=ax, vmin=-0.01, vmax=0.01)\n",
"\n",
"ani = animation.FuncAnimation(\n",
" fig, partial(update_frame_pressure, time_stamps=time_stamps),\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit strange that you use partial function application here, when you have full control over the function. You could simply move the time_stamps into the function.

But even better, you could use the time_stamps as frames:

def update_frame_pressure(t):
    p = sfs.td.synthesize(
        d, selection, array, secondary_source, grid=grid, observation_time=t)
    im.set_array(p)
    return [im]

ani = animation.FuncAnimation(
    fig, update_frame_pressure, frames=time_stamps,
    interval=interval, blit=True)

"fig, ax = plt.subplots(figsize=(5, 5))\n",
"p = sfs.td.synthesize(d, selection, array, secondary_source, grid=grid,\n",
" observation_time=0)\n",
"im = plot(d, selection, secondary_source, t=ts, ax=ax, vmin=-0.01, vmax=0.01)\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This frame is not used in the final animation, so the value of t is not really relevant, and defining ts is not necessary.

"outputs": [],
"source": [
"# Animation\n",
"def plot(d, selection, secondary_source, t=0, ax=None, **kw):\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I'm a fan of reusable plot functions, especially if users can copy it and use it in their own code. However, this doesn't work here, because grid and array are not passed as function arguments.

I'm also not sure if it helps the understanding here, in the context of the animation, what do you think?

I think you should either add more arguments or completely dissolve the function. I could imagine a few notebook cells with easily digestible steps like this:

Create loudspeaker driving signals.

delays, weights, selection, secondary_source = sfs.td.wfs.point_25d(
    array.x, array.n, xs)
d = sfs.td.wfs.driving_signals(delays, weights, signal)

Create one frame of the initial sound field for the initial plot.
The observation_time doesn't matter,
because this frame will not be used in the final animation.

p = sfs.td.synthesize(
    d, selection, array, secondary_source, grid=grid, observation_time=0)

Create the initial plot (but don't show it yet).

fig, ax = plt.subplots(figsize=(5, 5))
im = sfs.plot2d.amplitude(p, grid, ax=ax, vmin=-0.01, vmax=0.01)
sfs.plot2d.loudspeakers(array.x, array.n, selection * array.a, size=0.15)
plt.close()

Define a function which will update im for each frame of the animation.

def update_frame_pressure(t):
    p = sfs.td.synthesize(
        d, selection, array, secondary_source, grid=grid, observation_time=t)
    im.set_array(p)
    return [im]

Put everything together to create an animation.

ani = animation.FuncAnimation(
    fig, update_frame_pressure, frames=time_stamps,
    interval=interval, blit=True)

"outputs": [],
"source": [
"# Save as gif file - This might take a few minutes.\n",
"ani.save(\"wfs-25d-td.gif\", writer='imagemagick',fps=10, dpi=200)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After creating the GIF file, you could also include it in a Markdown cell like this:

![](wfs-25d-td.gif)

BTW, why are you specifying fps here and not above when using to_jshtml()?

"metadata": {},
"outputs": [],
"source": [
"# Save as gif file - This might take a few minutes.\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever I write that something takes a long time, I also try to show how long it actually takes. You could use the %%time magic here:

%%time
ani.save("wfs-25d-td.gif", writer='imagemagick', fps=10, dpi=200)

@fs446
Copy link
Member

fs446 commented Sep 9, 2025

I added some ideas in the branch td-wfs-animation-fscom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants