Spatial Audio¶
3D positional sound that pans as the source orbits you.
📄 Docs onlyTags: audio 3d spatial
What it demonstrates¶
AudioPlayer3Dattached to a moving mesh, so the mix pans as the source moves.AudioListener3Dparented under theCamera3D, so the camera pose is the ear.A procedurally generated looping tone via
AudioClip.tone()(no external asset).Live spatial state: the source orbits the world origin, so both its direction and its distance from the listener change every frame. The stereo image sweeps left to right and the level swells as the sphere passes near the camera.
Distance is measured between the listener’s world position and the source’s, so the orbit runs from about 5 units away (roughly -5 dB) to about 15 (roughly -48 dB) with the inverse-square falloff set below.
Controls¶
ESC: quit.
Source¶
1"""Spatial Audio: 3D positional sound that pans as the source orbits you.
2
3# /// simvx
4# tags = ["audio", "3d", "spatial"]
5# [web]
6# root = "SpatialAudio"
7# width = 800
8# height = 600
9# responsive = true
10# disabled = true
11# reason = "Browser audio unlocks only on a user gesture and this scene starts its tone on load, so it would be silent."
12# ///
13
14## What it demonstrates
15- `AudioPlayer3D` attached to a moving mesh, so the mix pans as the source moves.
16- `AudioListener3D` parented under the `Camera3D`, so the camera pose is the ear.
17- A procedurally generated looping tone via `AudioClip.tone()` (no external asset).
18- Live spatial state: the source orbits the world origin, so both its direction and
19 its distance from the listener change every frame. The stereo image sweeps left to
20 right and the level swells as the sphere passes near the camera.
21
22Distance is measured between the listener's world position and the source's, so the
23orbit runs from about 5 units away (roughly -5 dB) to about 15 (roughly -48 dB) with
24the inverse-square falloff set below.
25
26## Controls
27- `ESC`: quit.
28"""
29
30import math
31
32from simvx.core import (
33 AudioClip,
34 AudioListener3D,
35 AudioPlayer3D,
36 Camera3D,
37 DirectionalLight3D,
38 Input,
39 Key,
40 Material,
41 Mesh,
42 MeshInstance3D,
43 Node,
44 Vec3,
45)
46from simvx.graphics import App
47
48ORBIT_RADIUS = 6.0 # World units the source orbits at.
49ORBIT_SPEED = 0.6 # Radians per second.
50
51
52class SpatialAudio(Node):
53 input_actions = {"quit": [Key.ESCAPE]}
54
55 def on_ready(self):
56 # Camera looks down the orbit plane from above and behind; it is the ear.
57 cam = self.add_child(Camera3D(name="Camera", position=Vec3(0, 4, 9), look_at=Vec3(0, 0, 0), fov=60.0))
58 cam.add_child(AudioListener3D(name="Listener"))
59 self.add_child(DirectionalLight3D()) # so the source and marker are lit
60
61 # A small marker mesh at the world origin the source orbits around.
62 origin = self.add_child(MeshInstance3D(name="OrbitCentre"))
63 origin.mesh = Mesh.cube()
64 origin.scale = Vec3(0.4, 0.4, 0.4)
65 origin.material = Material(colour=(0.9, 0.9, 0.2, 1.0), roughness=0.6)
66
67 # The moving sound source: a small sphere that orbits the origin marker.
68 self.source = self.add_child(MeshInstance3D(name="Source", position=Vec3(ORBIT_RADIUS, 0, 0)))
69 self.source.mesh = Mesh.sphere()
70 self.source.scale = Vec3(0.5, 0.5, 0.5)
71 self.source.material = Material(colour=(0.2, 0.6, 1.0, 1.0), roughness=0.3)
72
73 # The 3D audio player rides on the source; its world position drives the mix.
74 self.player = self.source.add_child(AudioPlayer3D(name="Tone"))
75 self.player.stream = AudioClip.tone(330.0, duration=2.0, volume=0.4)
76 self.player.loop = True
77 self.player.max_distance = 20.0 # Inaudible past this radius (the orbit stays inside it).
78 self.player.attenuation = 2.0 # Inverse-square falloff.
79 self.player.play()
80
81 self._angle = 0.0
82
83 def on_update(self, dt: float):
84 if Input.is_action_just_pressed("quit"):
85 self.app.quit()
86 return
87
88 # Orbit the source around the origin so its distance and direction from the camera change.
89 self._angle += ORBIT_SPEED * dt
90 self.source.position = Vec3(math.cos(self._angle) * ORBIT_RADIUS, 0.0, math.sin(self._angle) * ORBIT_RADIUS)
91
92
93if __name__ == "__main__":
94 App(title="Spatial Audio", width=800, height=600).run(SpatialAudio())