Spatial Audio¶
3D positional sound with distance attenuation.
📄 Docs onlyTags: audio 3d spatial
What it demonstrates¶
AudioPlayer3Dattached to a moving mesh so the mix pans and attenuates with distance.AudioListener3Dparented under theCamera3D, so the camera pose is the ear.A procedurally generated looping tone via
AudioClip.tone()(no external asset).Live spatial state: listener->source distance changes as the source orbits the listener.
Source¶
1"""Spatial Audio: 3D positional sound with distance attenuation.
2
3# /// simvx
4# tags = ["audio", "3d", "spatial"]
5# web = { root = "SpatialAudio", width = 800, height = 600, responsive = true, disabled = true, reason = "Audio playback needs a device; the web backend renders the scene but stays silent." }
6# ///
7
8## What it demonstrates
9- `AudioPlayer3D` attached to a moving mesh so the mix pans and attenuates with distance.
10- `AudioListener3D` parented under the `Camera3D`, so the camera pose is the ear.
11- A procedurally generated looping tone via `AudioClip.tone()` (no external asset).
12- Live spatial state: listener->source distance changes as the source orbits the listener.
13"""
14
15import math
16
17from simvx.core import (
18 AudioClip,
19 AudioListener3D,
20 AudioPlayer3D,
21 Camera3D,
22 DirectionalLight3D,
23 Material,
24 Mesh,
25 MeshInstance3D,
26 Node,
27 Vec3,
28)
29from simvx.graphics import App
30
31ORBIT_RADIUS = 6.0 # World units the source orbits at.
32ORBIT_SPEED = 0.6 # Radians per second.
33
34
35class SpatialAudio(Node):
36 def on_ready(self):
37 # Camera looks down the orbit plane from above and behind; it is the ear.
38 cam = self.add_child(Camera3D(name="Camera", position=Vec3(0, 4, 9), look_at=Vec3(0, 0, 0), fov=60.0))
39 cam.add_child(AudioListener3D(name="Listener"))
40 self.add_child(DirectionalLight3D()) # so the source and marker are lit
41
42 # A small marker mesh at the listener origin for visual reference.
43 origin = self.add_child(MeshInstance3D(name="ListenerMarker"))
44 origin.mesh = Mesh.cube()
45 origin.scale = Vec3(0.4, 0.4, 0.4)
46 origin.material = Material(colour=(0.9, 0.9, 0.2, 1.0), roughness=0.6)
47
48 # The moving sound source: a small sphere that orbits the listener.
49 self.source = self.add_child(MeshInstance3D(name="Source", position=Vec3(ORBIT_RADIUS, 0, 0)))
50 self.source.mesh = Mesh.sphere()
51 self.source.scale = Vec3(0.5, 0.5, 0.5)
52 self.source.material = Material(colour=(0.2, 0.6, 1.0, 1.0), roughness=0.3)
53
54 # The 3D audio player rides on the source; its world position drives the mix.
55 self.player = self.source.add_child(AudioPlayer3D(name="Tone"))
56 self.player.stream = AudioClip.tone(330.0, duration=2.0, volume=0.4)
57 self.player.loop = True
58 self.player.max_distance = 12.0 # Inaudible past this radius.
59 self.player.attenuation = 2.0 # Inverse-square falloff.
60 self.player.play()
61
62 self._angle = 0.0
63
64 def on_update(self, dt: float):
65 # Orbit the source around the listener so distance and direction change.
66 self._angle += ORBIT_SPEED * dt
67 self.source.position = Vec3(math.cos(self._angle) * ORBIT_RADIUS, 0.0, math.sin(self._angle) * ORBIT_RADIUS)
68
69
70if __name__ == "__main__":
71 App(title="Spatial Audio", width=800, height=600).run(SpatialAudio())