flutter3d_audio 0.8.0
flutter3d_audio: ^0.8.0 copied to clipboard
Positional audio for flutter3d: attenuation, panning and voice limiting, with a pluggable backend.
flutter3d_audio #
Positional audio: attenuation, panning, occlusion and voice limiting, with a
pluggable backend. It is a sibling of flutter3d_game, not a RenderPlugin:
it draws nothing, so the renderer's plugin seam does not apply to it.
It depends on neither flutter3d nor flutter_gpu. It is handed a listener
pose and emitter positions, and where those come from is up to the application.
Why the geometry is computed here #
SoLoud has a 3D layer, but it cannot be used for anything that moves. Applying a
moved source or a turned listener requires update3dAudio(), and
flutter_soloud 4.1.7 exposes neither that call nor anything that makes it. Its
set3dSourcePosition passes straight through to a SoLoud method that only marks
the source dirty. A source set once at play3d never moves again, and in a
first-person game the listener turns constantly.
So AudioScene computes gain and pan, and the backend is asked only for a flat
voice with a volume and a pan. Every backend can do that, and it takes effect
immediately. If a later flutter_soloud exposes the call, the right change is a
second backend, not an edit to this one.
Shape #
final scene = AudioScene(
backend: SoLoudBackend()..open(),
maxVoices: 24,
// The walls belong to the physics; this package must not learn about them.
occlusion: (from, to) => world.raycast(from, to) ? 0.3 : 1.0,
);
await scene.preload(<SoundDef>[Sounds.pistol, Sounds.door]);
scene.play(Sounds.pistol, muzzlePosition); // one-shot
final hum = scene.play(Sounds.torch, torchPosition); // holds a looping emitter
// Once a frame, after everything has moved.
listener.aimAt(eyePosition, yaw);
scene.update(listener);
play returns an emitter so whatever owns a moving source can move it. The
mixer updates that voice instead of restarting it.
What the mixer decides #
- Attenuation is
InverseRolloffby default, the only one of the three that sounds right when the player walks past something.LinearRolloffandExponentialRolloffexist because designers ask for them, andNoAttenuationis for music. - Panning uses the left-right component of the direction and nothing else. It is yaw only, so tilting your head back does not swap left and right.
- Voice limiting ranks by priority first and loudness second. A door closing outranks the ninth footstep even when the footstep is nearer.
- With instance limiting, ten identical grunts on one frame are a click instead of ten times as loud.
Testing #
SilentBackend records every call and makes no sound, so the whole mix is
testable without an audio device. A headless build, or a player who has turned
sound off, should use it too, instead of a branch at every call site.
Part of flutter3d, an independent
implementation of a 3D engine for Flutter. It is not a fork or a binding of
another engine, and it is not affiliated with the Flutter team. It has four
switchable rendering backends: Impeller via Flutter GPU, WebGL2, WebGPU and a
software rasteriser. It loads glTF, OBJ and .f3d, and has six lighting models,
shadows, bloom, skinning, animation, BVH culling and picking, plus a
deterministic fixed-step game layer with collision, navigation, positional
audio, and gamepad and touch input. Four example games (shooter, platformer,
racing, strategy) are each built on a genre package:
flutter3d_game_shooter,
flutter3d_game_platformer,
flutter3d_game_racing,
flutter3d_game_strategy.
A new game starts from the editor's scaffold, which writes one from a template:
https://flutter3d.pleion.dev/first-project/. Documentation:
https://flutter3d.pleion.dev.