Sound rays bend into the ocean's deep channel, lighting up caustics and shadow zones.
Sound is slowest about a kilometer down, where the water is cold but the pressure is not yet high, and rays always bend toward slower water, so sound launched near that depth stays trapped. Thousands of rays from a whale are integrated through realistic sound speed profiles with the ray equations, bouncing off the surface and seabed with losses. The ocean here does not change with range, so each ray's path is periodic: one cycle is traced and repeated for up to 4,000 km. Rays are splatted into an accumulation buffer and tone mapped, so convergence zones glow where rays crowd and shadow zones stay dark. Neighboring rays that bracket the hydrophone give its eigenrays: steep paths arrive first and the strong axial arrival comes last.
Try it. Drag the whale up or down to change the source depth, and click or drag in the water to move the hydrophone. Switch between temperate, tropical, winter and Arctic oceans and between 200, 1,000 and 4,000 km, and widen the launch angles. Click Hear the whale to play the call, then the same call as the hydrophone hears it, smeared by multipath. Keys: 1 to 4 for the oceans, R for the range, the arrows for source depth and launch angles, Space to play.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a visualization of sound traveling through the ocean's SOFAR channel with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Make a canvas that fills the window, stays sharp on high-DPI screens, and resizes with the window. Show an ocean cross-section: range 0 to 200 km left to right, depth 0 to 5,000 m top to bottom, on a dark navy background.
- Use the Munk sound speed profile: c(z) = 1500 * (1 + 0.00737 * (eta - 1 + exp(-eta))) with eta = 2 * (z - 1300) / 1300.
- From a source at 1,000 m depth, launch about 300 rays with angles between -14 and +14 degrees. Step each ray along its path with dr = cos(theta) ds, dz = sin(theta) ds, dtheta = -cos(theta) * c'(z) / c(z) ds, using ds of about 150 m. Reflect rays off the surface and the seabed.
- Draw every ray as a thin line with low alpha and globalCompositeOperation = "lighter", so the places where rays bunch up glow.
Once that works, make it beautiful:
- Draw the sound speed profile in a narrow strip on the left, sharing the depth axis, and mark the channel axis where the speed is lowest.
- Raise the ray count to a few thousand by drawing into an accumulation buffer (a Float32Array the size of a small offscreen canvas) and tone mapping it with a log curve and a blue-to-white color ramp. The convergence zones and shadow zones will pop out.
- Let me drag the source depth with the mouse and watch the pattern rebuild.
- Animate a pulse: dots riding each ray at the same travel time, so the folded wavefront sweeps across.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as an Arctic profile where the channel is right under the ice, a hydrophone that lists arrival times of the rays reaching it, or playing a whale call smeared by those arrivals with the Web Audio API.